【Java学习笔记】 动态代理

文章目录

    • 动态代理
      • 1、什么是动态代理?
        • 动态代理常见的实现技术包括以下三种
      • 2、Java内置的动态代理
        • 1)如何使用Proxy
        • 2)调用处理器InvocationHandler
          • **思考:为什么要强制实现InvocationHandler接口呢?**
          • 思考2:invoke方法什么时候被调用?(如何调用?)
        • 3)invoke方法的使用
          • 那么,如何使用Method来调用目标方法呢?
        • 4)invoke方法的返回值
        • 5)进一步封装一个自定义的工具方法
      • 3、CGLIB 动态代理
        • 1)什么是CGLIB(和JDK动态代理的区别
        • 2)用法/写法
        • 3)回调如何设置?
          • 4)测试并观察代理对象的运行结果
            • 测试代码如下:

    动态代理

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

    版权声明:本文为博主作者:Xcong_Zhu原创文章,版权归属原作者,如果侵权,请联系我们删除!

    原文链接:https://blog.csdn.net/Xcong_Zhu/article/details/129967719

    共计人评分,平均

    到目前为止还没有投票!成为第一位评论此文章。

    (0)
    心中带点小风骚的头像心中带点小风骚普通用户
    上一篇 2024年1月8日
    下一篇 2024年1月8日

    相关推荐