GoF之动态代理

动态代理
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
在内存当中动态生成类的技术常见的包括:
● JDK动态代理技术:只能代理接口。
● CGLIB动态代理技术:它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。
    它可以在运行期扩展Java类与实现Java接口。(底层有一个小而快的字节码处理框架ASM。)
● Javassist动态代理技术:

JDK动态代理:
    在JDK动态代理中一般有这几个步骤:
        1. 创建目标对象
        2. 创建代理对象 : 通过Proxy.newProxyInstance()进行创建 --并且代理对象和目标对象实现的接口要一样,也就是需要向下转型
        3. 调用代理对象中的代理方法
    在创建代理对象的时候,用到了newProxyInstance方法 --> 翻译为新建代理对象
        也就是说,通过这个方法可以创建代理对象,本质上,这个方法的执行做了两件事:
            1.在内存中动态生成了一个代理类的字节码class
            2.new 对象了,通过内存中生成的代理类代理这个代码,实例化了代理对象
    关于newProxyInstance方法中的三个参数:
        1.ClassLoader loader    -->  (类加载器)
        在内存中生成的字节码也是class文件,如果需要执行就需要先加载到内存中,加载器就需要类加载器,所以这里需要指定类加载器
        并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个

        2.Class[] interfaces -->  (接口)
        代理类和目标类要实现同一个接口或者同一些接口,在内存中生成代理类的时候,这个代理类需要告诉他实现了哪些接口

        3.InvocationHandler h   -->  (调用处理器--是一个接口)
        在调用处理器接口中编写的就是 : 增强代码,既然是接口,就需要写接口的实现类 ,
        这个不会出现类爆炸,主要原因是:这种调用处理器写一次就可以了
代码如下:
OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
                          target.getClass().getInterfaces(),new TimerInvocationHandler(target))

    关于InvocationHandler的实现:
        1.目标对象(也就是定义成员变量)
        2.通过构造器赋值给成员变量
        3.实现接口中的invoke()方法
    invoke()方法什么时候被调用?被谁调用呢?
        当代理独享调用代理方法的时候,注册在InvocationHandler调用处理器接口当中的invoke()方法就被调用了
        这个方法并不是我们来调用的,是JDK负责调用的
    invoke()方法的三个参数:JDK 在调用这个方法的时候,会自动给我们传过来三个参数:
        1. Object proxy : 代理对象的引用。这个参数使用较少。
        2. Method method 目标对象上的目标方法。(要执行的目标方法就是它。也就是说,在执行过程中,使用method来调用目标对象的目标方法)
        3. Object[] args : 目标方法上的实参

代码的实现如下:
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 这个接口的目的就是为了让你有地方写增强代码。
        //System.out.println("增强1");
        long begin = System.currentTimeMillis();

        // 调用目标对象上的目标方法
        // 方法四要素:哪个对象,哪个方法,传什么参数,返回什么值。
        Object retValue = method.invoke(target, args);

        //System.out.println("增强2");
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");

        // 注意这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回。
        return retValue;
    }

/*接口和目标对象就不创建了 ,自行脑补*/
public class Client {
    //客户端程序
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
        OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
                                                target.getClass().getInterfaces(),
                                                new TimerInvocationHandler(target));
        // 调用代理对象的代理方法 注意:调用代理对象的代理方法的时候,如果你要做增强的话,目标对象的目标方法得保证执行。
        proxyObj.generate();
        proxyObj.modify();
        proxyObj.detail();
        String name = proxyObj.getName();
        System.out.println(name);
    }
}
public class TimerInvocationHandler implements InvocationHandler {
    private Object target;  // 目标对象
    public TimerInvocationHandler(Object target) {// 赋值给成员变量。
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 这个接口的目的就是为了让你有地方写增强代码。
        long begin = System.currentTimeMillis();
        // 调用目标对象上的目标方法
        // 方法四要素:哪个对象,哪个方法,传什么参数,返回什么值。
        Object retValue = method.invoke(target, args);
        //System.out.println("增强2");
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
        // 注意这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回。
        return retValue;
    }
}
CGLIB动态代理: 底层本质上:
    CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。
    1.添加 CGLIB 的相关依赖
    2.准备一个没有实现接口的类UserService
    3.使用CGLIB在内存中为UserService类生成代理类,并创建对象:主要分为以下步骤:
        a.创建字节码增强器对象,这个对象是CGLIB库当中的核心对象,就是依靠它来生成代理类。new Enhancer();
        b.告诉CGLIB父类是谁,也就是告诉CGLIB目标类是谁 : enhancer.setSuperclass(UserService.class);
        c.设置回调接口(等同于JDK动态代理当中的调用处理器。InvocationHandler 但是在CGLIB当中不是InvocationHandler接口,
        是方法拦截器接口:MethodInterceptor) enhancer.setCallback(方法拦截器对象);
        d.创建代理对象(会做两件事:1.在内存中生成UserService类的子类,其实就是代理类的字节码
                              2.创建代理对象)父类是UserService,子类这个代理类一定是UserService
          UserService userServiceProxy = (UserService) enhancer.create();
        e.调用代理对象的代理方法
    MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:
    Object target:目标对象
    Method method:目标方法
    Object[] objects:目标方法调用时的实参
    MethodProxy methodProxy:代理方法

public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器对象 这个对象是CGLIB库当中的核心对象,就是依靠它来生成代理类。
        Enhancer enhancer = new Enhancer();
        // 告诉CGLIB父类是谁。告诉CGLIB目标类是谁。
        enhancer.setSuperclass(UserService.class);
        // 设置回调(等同于JDK动态代理当中的调用处理器。InvocationHandler)在CGLIB当中不是InvocationHandler接口,是方法拦截器接口:MethodInterceptor
        enhancer.setCallback(new TimerMethodInterceptor());
        // 创建代理对象
        UserService userServiceProxy = (UserService) enhancer.create();
        // 调用代理对象的代理方法。
        boolean success = userServiceProxy.login("admin", "123");
        System.out.println(success ? "登录成功" : "登录失败");
        userServiceProxy.logout();
    }
}
public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前面增强
        long begin = System.currentTimeMillis();
        // 怎么调用目标对象的目标方法呢?
        Object retValue = methodProxy.invokeSuper(target, objects);
        // 后面增强
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
        return retValue;
    }
}
public class UserService {
    // 目标方法
    public boolean login(String username, String password){
        System.out.println("系统正在验证身份...");
        if ("admin".equals(username) && "123".equals(password)) {
            return true;
        }
        return false;
    }
    // 目标方法
    public void logout(){
        System.out.println("系统正在退出...");
    }
}

注意:对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数

--add-opens java.base/java.lang=ALL-UNNAMED
●--add-opens java.base/sun.net.util=ALL-UNNAMED

GoF之动态代理_第1张图片

 

你可能感兴趣的:(Spring,java)