java 代理模式 总结

1.前言

  最近舍友去面试遇到了关于java代理模式的问题。

  我虽然知道怎么使用,但是没有做过正经的总结,因此有了这篇随笔,好好总结一下三大代理模式底层原理。

事实上,在开发项目的时候,基本用不上代理,一般使用代理都是修改以前的代码才用到了,没人闲着在业务层多套一层代理吧???

  为什么使用代理?

  原因是不希望影响原有的业务代码的同时,添加自定义的操作,如写日志、权限拦截、发短信、发邮件等,这样的操作叫做增强操作,
但是这一般是aop【面向切面编程】做的,虽然aop底层就是使用代理做的,但是用aop方便很多,因为使用aop完全不需要改变原有任何的业务代码,
而代理还需要套一层代理类。这就是差距。
  事实上不论是代理还是aop,根本上都是竖向执行业务的,有先后循序,为了解决这个问题,后来有了消息中间件 ,可以横向执行业务,当然这都是后话。
因此 不论是 aop还是代理,更多的是希望在尽可能不改变原有的业务代码而加入其他操作,至于喜欢使用aop还是代理都是看自己的喜好了。

  代理模式分静态代理、动态代理、CGLib代理,

注意了 :

动态代理  也叫  JDK代理 、接口代理 ,
Cglib代理 也叫  子类代理

  那么这些代理模式的关系是什么?

  一开始并没有所谓的代理概念,为了解决增强操作,其实一开始就是目标类外层套一个新类做新的业务,需要执行目标类方法时,直接实例目标类执行而已,
并没有什么特别的东西,后来有心人将这种增强写法规范了格式,在代理类实现了与目标类一样的接口,重写接口内容,在里面做增强业务,不仅可以确保不写错,
还可以不用手写,这就很舒服了,于是就有了现在的静态代理写法,但是这种增强操作缺点也是显然易见的,
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护。
  是不是很烦?于是出来了个叫动态代理的东西,解决了静态代理的缺点,而且在代理类还不需要实现与目标类一样的接口就可以达到一样的目的,
可是,虽然代理类没有直接实现在目标类所实现的接口,但是在内存动态的创建代理对象 方法的注入参数 里 需要 目标对象实现的接口的类型 ,
因此目标类必须要最少实现一个接口 ,否则无法使用动态代理。
  那这就出了个很大的问题,有些目标类不是业务层的,仅仅是一个单独的类 ,没有实现任何接口,是不能使用动态代理的。
  这又该怎么办?
  好家伙,又出了个叫 CGLib代理的东西 ,完美解决了 动态代理的缺点,继承了动态代理的优势 ,又不需要目标类需要实现接口的要求,
但是CGLib代理 不是内置在jdk里的,需要第三方jar包导入才能使用,其实就是个底层为 ASM字节码框架 的 代码生成包,不建议直接操作ASM。
【动态代理是内置在jdk的,这就是动态代理也叫jdk代理的原因】

  总结

静态代理总结:
(1)其实,所谓的静态代理,其实就是创建一个类【名为代理类】,然后该类实现了与目标类【业务层对象】一样的接口后,
重写接口内容,里面做增强业务,也就是自定义操作,然后需要执行目标类的方法时,调用注入的目标类[即业务层对象]的方法即可,
说白了就是目标类外再套一层方法而已。
(2)重写目的是保证与目标类的方法一样,不仅可以确保不写错,还可以不用手写,这就很舒服了,
但是并不是说目标类必须要有接口才可以使用静态代理,因此,目标类不论是否有接口都可以使用静态代理。
(3)缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护。
动态代理总结:
(1)动态代理又称为jdk代理、接口代理。
(2)动态代理解决静态代理的缺点,代理类不需要再与目标对象实现一样的接口。
(3)实例代理类后,注入目标类对象即可,然后调用代理类的方法会在内存动态的创建代理对象【使用Proxy.newProxyInstance】后将该代理对象返回,
然后强转成目标对象一样的类型即可按照目标类型方法调用,创建代理对象需要输入的参数 分别是 目标类的类加载器 、目标对象实现的接口的类型、事件处理器 ,
然后重写事件处理器里的方法,该方法内写自定义的增强业务,而目标对象的方法调用则固定使用 Object returnValue = method.invoke(target, args);完成 ,
因此,如果需要对调用的当前方法分别做不同的操作,可以使用method.getName()获取当前执行的方法名字后判断,然后执行不同的增强操作,
那么如果需要对不同的目标类做不同的操作呢?那么可以使用target.getClass().getName()获取当前注入的目标类对象的名字。
这么做的好处就是不需要创建大量动态代理类做不同的增强操作,这是与静态代理最大的区别,但是我感觉这样做会变不伦不类了。
这样做的流程是 实例代理类-》注入目标对象-》内存创建代理对象-》判断当前注入对象名称-》判断当前执行的方法-》做增强业务-》返回结果
(4)虽然代理类没有直接实现在目标类所实现的接口,但是在内存动态的创建代理对象 方法的注入参数需要 目标对象实现的接口的类型 ,
因此目标类必须要最少实现一个接口 ,否则无法使用动态代理。
(5)缺点:有些类不是业务层的,仅仅是一个单独的类 ,没有实现任何接口,是不能使用动态代理的。
CGLib代理总结:
(1)记得需要导入 cglib依赖包 ,这其实是个代码生成包,在内存中构建一个子类对象从而实现对目标对象功能的扩展。
(2)Cglib 代理 又称为 子类代理。
(3)代理类需要实现MethodInterceptor接口 ,用于重写拦截方法【intercept()】,内部的增强操作方式 与 动态代理十分相似。
(4)创建代理对象需要使用工具类Enhancer。
(5)允许代理那些那么没有实现任何接口的目标类,解决了动态代理的缺点。
(6)底层是ASM字节码框架,不建议直接操作ASM。

2.静态代理

(1)目录结构

 服务层接口

package com.example.javabaisc.proxy.mstatic.service;

public interface UserService {
    void getname();
}
View Code

接口实现类【即目标类】

package com.example.javabaisc.proxy.mstatic.service.serviceImpl;

import com.example.javabaisc.proxy.mstatic.service.UserService;

public class UserServiceImpl implements UserService {
    @Override
    public void getname() {
        System.out.println("老王跑啦");
    }
}
View Code

静态代理类

package com.example.javabaisc.proxy.mstatic.mproxy;

import com.example.javabaisc.proxy.mstatic.service.UserService;
import com.example.javabaisc.proxy.mstatic.service.serviceImpl.UserServiceImpl;

import java.util.Date;

/**
 * 代理类
 */
public class UserServiceProxy implements UserService {
    //final可有可不有 ,但是最后idea提示最好加上去
    //类型定义必须与注入类型对应,否则需要强转
    private final UserServiceImpl target;

    //有参构造函数,用于注入业务层的实现类
    public UserServiceProxy(UserServiceImpl target){
            this.target = target;
    }

    @Override
    public void getname() {
        System.out.println("进入静态代理类,准备开始增强业务");
        System.out.println("老王人哪去了?");
        target.getname();
        System.out.println("准备退出静态代理类,增强业务结束,"+new Date());
    }
}
View Code

测试类

package com.example.javabaisc.proxy.mstatic;


import com.example.javabaisc.proxy.mstatic.mproxy.UserServiceProxy;
import com.example.javabaisc.proxy.mstatic.service.UserService;
import com.example.javabaisc.proxy.mstatic.service.serviceImpl.UserServiceImpl;
import org.junit.jupiter.api.Test;

/**
 * 静态代理
 */

public class MSTest {

    //无静态代理
    @Test
    public void t() {
        //实例业务层对象【类型也可以使用接口的】
        UserServiceImpl userService = new UserServiceImpl();
        //执行业务层
        userService.getname();
    }

    //使用静态代理
    @Test
    public void t2() {
        //实例业务层对象【类型也可以使用接口的】
        UserServiceImpl userService = new UserServiceImpl();
        //实例代理类,并注入业务层对象
        UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
        //执行代理业务
        userServiceProxy.getname();
    }

}
View Code

(2)执行测试类 t()方法

打印结果

 (3)执行测试类 t2()方法

打印结果

 3.动态代理

(1)目录结构

 服务层接口

package com.example.javabaisc.proxy.mdynamic.service;

public interface UserService {
    void getname();
}
View Code

接口实现类【即目标类】

package com.example.javabaisc.proxy.mdynamic.service.serviceImpl;

import com.example.javabaisc.proxy.mdynamic.service.UserService;

public class UserServiceImpl implements UserService {
    @Override
    public void getname() {
        System.out.println("老王跑啦");
    }
}
View Code

动态代理类

package com.example.javabaisc.proxy.mdynamic.mproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;

/**
 * 代理类
 */

public class DynamicProxy {

    //目标对象
    //final可有可不有 ,但是最后idea提示最好加上去
    private final Object target;

    //有参构造函数,用于注入业务层的实现类
    public DynamicProxy(Object target) {
        this.target = target;
    }
    //方法名可随意定义,这个无关紧要,目的是调用newProxyInstance去内存动态创建代理对象
    public Object getProxyInstance() {
        //到内存动态创建代理对象,参数分别是:目标类的类加载器 、目标对象实现的接口的类型、事件处理器
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    //重写事件处理器的调用方法
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("进入动态代理类,准备开始增强业务");
                        System.out.println("老王人哪去了?");
                        System.out.println("目标类名字:"+target.getClass().getName());
                        System.out.println("方法名:"+method.getName());
                        //执行目标对象方法,参数分别是 目标类对象 、当前方法注入参数
                        Object returnValue = method.invoke(target, args);
                        System.out.println("准备退出动态代理类,增强业务结束," + new Date());
                        return returnValue;
                    }
                });
    }

}
View Code

测试类

package com.example.javabaisc.proxy.mdynamic;

import com.example.javabaisc.proxy.mdynamic.mproxy.DynamicProxy;
import com.example.javabaisc.proxy.mdynamic.service.UserService;
import com.example.javabaisc.proxy.mdynamic.service.serviceImpl.UserServiceImpl;
import org.junit.jupiter.api.Test;

/**
 * 动态代理 ,也叫jdk代理 、接口代理
 */
public class MDTest {

    @Test
    public void t(){

        //实例目标对象【类型是接口或实现类都可以,我喜欢用接口】
        UserService userService = new UserServiceImpl();
        //实例动态代理对象,注入目标对象,然后在内存创建该目标对象的代理对象,返回结果强转成目标对象一样的类型
        //这样好处就是只要增强操作是一样的,则可以统一使用这个动态代理类
        UserService userServiceProxy = (UserService)new DynamicProxy(userService).getProxyInstance();
        //执行代理对象,方法则与目标类的相同
        userServiceProxy.getname();



    }
}
View Code

(2)启动测试方法

打印结果

  4.CGLib代理

(1)目录结构

 添加依赖包,目前最新版本3.3.0

<!-- Cglib代理 依赖包-->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
         </dependency>       

无实现接口的目标类

package com.example.javabaisc.proxy.mCglib.Util;


import org.springframework.stereotype.Service;

@Service
public class Mail {
    public void send(){
        System.out.println("发送邮件成功");
    }

}
View Code

CGLib代理类

package com.example.javabaisc.proxy.mCglib.mproxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Date;

public class CglibProxy implements MethodInterceptor {
    //目标对象
    //final可有可不有 ,但是最后idea提示最好加上去
    private final Object target;

    //有参构造函数,用于注入业务层的实现类
    public CglibProxy(Object target) {
        this.target = target;
    }

    //给目标对象创建一个代理对象
    public Object getProxyInstance(){
        //工具类
        Enhancer en = new Enhancer();
        //设置父类
        en.setSuperclass(target.getClass());
        //设置回调函数
        en.setCallback(this);
        //创建子类(代理对象)
        return en.create();

    }

    //重写拦截方法
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("进入Cglib代理类,准备开始增强业务");
        System.out.println("准备发送邮件");
        System.out.println("目标类名字:"+target.getClass().getName());
        System.out.println("方法名:"+method.getName());
        //执行目标对象方法,参数分别是 目标类对象 、当前方法注入参数
        Object returnValue = method.invoke(target, objects);
        System.out.println("准备退出Cglib代理类,增强业务结束," + new Date());
        return returnValue;
    }
}
View Code

启动类

package com.example.javabaisc.proxy.mCglib;

import com.example.javabaisc.proxy.mCglib.Util.Mail;
import com.example.javabaisc.proxy.mCglib.mproxy.CglibProxy;

import org.junit.Test;

import org.junit.runner.RunWith;
import org.junit.runner.Runner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest
@RunWith(SpringRunner.class)
public class CGTest {

    @Test
    public void t() {
        //实例目标类
        Mail mail = new Mail();
        //实例 Cglib 代理类 ,注入目标对象 ,创建代理对象,返回结果强转成目标对象一样的类型
        Mail mailProxy = (Mail) new CglibProxy(mail).getProxyInstance();
        //执行代理对象,方法则与目标类的相同
        mailProxy.send();

    }

    
    @Autowired
    private Mail mail;

    /**
     *使用javabean注解,不使用new实例化目标类
     */
    @Test
    public void t2() {
        
        //实例 Cglib 代理类 ,注入目标对象 ,创建代理对象,返回结果强转成目标对象一样的类型
        Mail mailProxy = (Mail) new CglibProxy(mail).getProxyInstance();
        //执行代理对象,方法则与目标类的相同
        mailProxy.send();

    }
}
View Code

(2)启动测试方法 t()

打印结果

 (3)如果使用 javabean注解来实例化 目标类,

在单元测试记得启动spring注解

 启动测试方法 t2()

打印结果

原文地址:https://www.cnblogs.com/c2g5201314/p/13138884.html