【Java学习笔记】 动态代理

动态代理

1、什么是动态代理?

前面已经提到了,动态代理就是【在内存】中动态生成【字节码代理类】的技术。(虽然不需要开发者书写,但是在内存层面依然存在该代理对象】

优点

  • 减少了代理类的数量
  • 并且解决了代码复用的问题。

动态代理常见的实现技术包括以下三种

  • JDK内置的动态代理技术 :只能代理接口
    • 位置:java.lang.reflect.Porxy ,是一个注解
  • CGLIB(Code Generation Library)动态代理技术,一个开源项目,生成类库,可以适用于接口和类
    • 但是CGLIB的低层是通过【继承】实现的(虽然是继承,但是由于是在内存动态生成字节码类,所以并不会增加耦合度),所以性能比JDK动态代理好
    • CGLIB的低层还有一个字节码处理框架 【ASM】(可能阅读源码时会遇到)
  • Javassist动态代理技术:东京大学的千叶滋教授所创建的开源项目。为JBOOS实现“aop”框架
    • mybatis框架底层就是用的javassist创建接口的字节码对象

Spring的低层主要是靠JDK内置的动态代理和CGLIB实现

2、Java内置的动态代理

1)如何使用Proxy

还是模拟静态代理的场景。接下来展示一下如何使用。

// 用法
OrderService target = new OrderServiceImpl();
Object proxyInstance = Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new TimerInvocationHandler());

image-20230331110214992

  • newProxyInstance,翻译就是新建代理对象,本质上该方法做了两件事
    • 在内存中动态的生成了一个代理类的字节码class
    • "new"对象了,通过内存中生成代理类,实例化了该代理对象
  • newProxyInstance() 方法有三个参数,分别的作用进行分析
    • ClassLoader loader ——类加载器,将字节码class文件加载到内存当中。而且JDK要求,目标类的类加载器,必须和代理类的类加载器使用同一个
    • Class<>?[] interfaces ——代理类和目标类实现的共同接口
    • InvocationHandler h —— 翻译是调用处理器

2)调用处理器InvocationHandler

这个参数的含义,在理解之前可以做一个简单的推测,我们在使用代理对象时,增强功能的代码应该写在哪里?(首先JDK肯定是不知道开发者要写什么代码的) 目前的三个参数已经用掉了两个,所以,不难推测调用处理器的作用。结合老杜的笔记,如下:

调用处理器的作用:写增强代码。同时InvocationHandler的接口,那我们就需要实现并重写该接口,再作为参数传入Proxy

重写后就可以发现,该接口需要重写一个方法——invoke()

public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;
思考:为什么要强制实现InvocationHandler接口呢?
  • 因为一个类实现接口必须实现接口中的方法
  • 以下的方法必须是invoke(),因为JDK在低层调用invoke的方法已经写好了
    • 也就是说,invoke方法并不是开发者调用,而是为了给JDK调用
思考2:invoke方法什么时候被调用?(如何调用?)
  1. 首先尝试重写invoke方法

    image-20230331111442117

  2. 调用target(目标对象)—— 还是调用原对象

    image-20230331111510461

  3. 尝试将newProxyInstance的返回值进行转型,向下转型为OrderService,并尝试调用。代码如下

    OrderService target = new OrderServiceImpl();
    
    OrderService obj  = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new TimerInvocationHandler());
    obj.insert();
    obj.modify();
    
    • 发现打印出INVOKE了,但是,目标对象的真正的方法无了,没有实现代理方法

      image-20230331111527380

3)invoke方法的使用

invoke方法有如下三个参数

image-20230331112001481

  • 第一个参数:代理对象的引用
  • 第二个参数,目标对象上的目标方法
  • 第三个参数,目标方法上的实参

核心思路: invoke方法在执行过程中,使用method方法来调用目标对象的目标方法

那么,如何使用Method来调用目标方法呢?

Method是Java反射的一个类,具体用法需要参照一下API给出的解释。

image-20230331114109966


也就是说方法四要素:哪个对象、哪个方法、传什么参数、传什么值。

而我们这边需要调用目标对象的目标方法,还缺一个关键 —— 目标对象

那么这里就需要使用构造函数将目标对象传入到该方法内以供调用,经过分得出以下方法,如下。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

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 {
    
    
        System.out.println(">>>BEFORE INVOKE ....");
        long begin = System.currentTimeMillis();
        Object retValue = method.invoke(target, args);
        long end = System.currentTimeMillis();
        System.out.println(">>>AFTER  INVOKE...COST:"+(end-begin));
        return null;
    }
}

客户端

public class Client {
    
    
    public static void main(String[] args) {
    
    
        OrderService target = new OrderServiceImpl();

        OrderService obj = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimerInvocationHandler(target));
        obj.insert();
        obj.modify();
    }
}

输出结果

image-20230331123043771

这里我们增强了普通的无返回值的方法,那么如何代理(增强)带返回值的方法呢?

4)invoke方法的返回值

对OrderService做如下修改,新增了一个带返回值的方法以便测试

image-20230331122754441

  • 尝试在上述代码中,不做修改,直接调用代理对象 —— 返回值为空

    image-20230331123023197

  • 那么在Invoke里 将 方法执行结果return。—— 即可得到预期的结果

    image-20230331123411425

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

至此,已经完成了通过JDK内置的代理类,实现使用代理模式增强业务代码的目的了。但是代码其实其实还是有点多,可以尝试使用工具类进行一个封装优化。

5)进一步封装一个自定义的工具方法

这里老杜的封装其实不是很严谨,应该传参还需要设置成可以传入一个自定义的处理器,这样这个工具类才会更加易用。改进后如下

工具类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyUtil {
    
    
    /**
     * 生成代理对象
     * @param target 目标对象
     * @param handler 调用处理器
     * @return 代理对象
     */
    public static Object getProxyInstance(Object target , InvocationHandler handler){
    
    
        Object proxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler);
        return proxyInstance;
    }
}

优化后客户端写法如下:

public class Client {
    
    
    public static void main(String[] args) {
    
    
        OrderService target = new OrderServiceImpl();
        TimerInvocationHandler timerInvocationHandler = new TimerInvocationHandler(target);
        OrderService obj = (OrderService) ProxyUtil.getProxyInstance(
            target, timerInvocationHandler);
    }
}

3、CGLIB 动态代理

老杜提到,这里的CGLIB写法并不是重点,需要了解一个大概的写法,并且明白,是使用继承的特性实现的动态代理。所以,CGLIB会更加的泛用。

1)什么是CGLIB(和JDK动态代理的区别

  • CGLIB既可以代理接口,又可以代理类
  • 低层是采用继承的方式是实现的
  • 所以被代理的目标类,不能使用final修饰
  • 另外。CGLIB功能更强大,效率也更高

2)用法/写法

  1. 既然提到CGLIB可以继承普通类,那么我们就直接声明一个普通类作为目标对象
  2. 创建客户端类 Client
  3. 创建字节码增强对象 Enhancer,作为CGLIB库中的核心对象,就是依靠它来生存代理类的
  4. 设置父类(告诉CGLIB父类,即目标类是谁)
  5. 设置回调(相当于JDK代理中的调用处理器,invocationHandler)
  6. 创建代理对象:做两件事
    • 在内存中生成目标对象的子类,其实就是代理类的字节码
    • 创建代理对象(通过代理类

3)回调如何设置?

实现的思路和JDK动态地理类似,都是实现一个接口

  • InvocationHandler (方法四要素)
  • MethodInterceptor (方法四要素)

不同之处在于,target目标对象不需要再手动创建构造函数传入了

如果运行报错 ,是因为JDK版本太高了,需要加参数

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

4)测试并观察代理对象的运行结果

image-20230405110401014

  • 结果是成功的,实现了业务增强的效果。

  • 老杜这里提到,要注意这个打印出来的对象地址是比较特殊的,可以用于以后调试的时候查看

    image-20230405110513212

测试代码如下:

业务逻辑:

package com.zhc.cglib.proxy.service;

public class OrderServiceCGlib {
    
    

    public void modify() {
    
    
        try {
    
    
            Thread.sleep(1500L);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("FINISH MODIFY");
    }


    public String getName() {
    
    
        System.out.println("GET NAME ...");
        return "ZHANGSAN";
    }
}

自定义方法拦截器:

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

import java.lang.reflect.Method;

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;
    }
}

客户端 :

package com.zhc.cglib.proxy.client;

import com.zhc.cglib.proxy.service.OrderServiceCGlib;
import com.zhc.cglib.proxy.service.TimerMethodInterceptor;
import net.sf.cglib.proxy.Enhancer;

public class CGlibClient {
    
    
    public static void main(String[] args) {
    
    
        /**
         * 1、创建增强器
         * 2、设置父类
         * 3、设置回调
         * 4、获取代理对象
         */
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderServiceCGlib.class);
        enhancer.setCallback(new TimerMethodInterceptor());
        OrderServiceCGlib orderServiceCGlib = (OrderServiceCGlib) enhancer.create();
        System.out.println(orderServiceCGlib.getName());
        orderServiceCGlib.modify();

        System.out.println(orderServiceCGlib);
    }
}

猜你喜欢

转载自blog.csdn.net/Xcong_Zhu/article/details/129967719