相关推荐recommended
通俗易懂 快速理解 JDK动态代理 和 cglib动态代理
作者:mmseoamin日期:2023-12-11
  • 动态代理的实现方案有两种,JDK动态代理和CGLIB动态代理,区别在于JDK自带的动态代理,必须要有接口,而CGLIB动态代理有没有接口都可以。

  • JDK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。

  • cglib动态代理:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。(CGLIB 通过动态生成一个需要被代理类的子类(即被代理类作为父类),该子类重写被代理类的所有不是 final 修饰的方法,并在子类中采用方法拦截的技术拦截父类所有的方法调用,进而织入横切逻辑。)

    没有实现接口或者不需要实现接口的类,我们可以通过cglib动态代理对它进行代理。

    基于cglib实现的动态代理需要引入第三方cglib库,之后我们可以实现基于子类的动态代理。

    使用cglib实现的动态代理也有一个约束条件,就是被代理类不能被final修饰,

    ​使用cglib实现的动态代理核心是Enhancer类,仔细观察我们会发现cglib动态代理的实现过程和JDK实现动态代理的过程极其类似。

    提示:务必仔细看代码注释!!!注释上有很详细的解释

    导航栏:

    • cglib动态代理
        • 测试demo结构:
        • 代码:
        • 运行结果:
        • 结论:
        • JDK动态代理
            • 代码:
            • 运行结果:
            • 结论:

              cglib动态代理

              注意:这里需要引入依赖:cglib 2.1_3.jar

              (如果不是maven项目,也可以手动导入cglib的jar包进行测试)

              
              	cglib
              	cglib
              	2.1_3
              
              

              测试demo结构:

              通俗易懂 快速理解 JDK动态代理 和 cglib动态代理,在这里插入图片描述,第1张

              代码:

              被代理类 Star:

              package com.tong;
              /**
               * @author tong
               * @Description:被代理类
               */
              public class Star {
                  public void sing() {
                      System.out.println("唱.......");
                  }
                  public void dance() {
                      System.out.println("跳......");
                  }
              }
              

              被代理类 ChineseCartoon:

              package com.tong;
              /**
               * 国漫:斗破苍穹
               *
               * @author tong
               * @Description:被代理类
               */
              public class ChineseCartoon {
                  public String person(String arg1, String arg2) {
                      if (arg1.equals("青莲地心火") && arg2.equals("陨落心炎")) {
                          System.out.println("斗破苍穹---萧炎");
                          return "萧炎";
                      }
                      return "123";
                  }
              }
              
              • 这里的create方法有两个参数,分别是Class type和Callback callback。
              • 其中Class type是指被代理类的字节码文件,因为有了被代理类的字节码后(即:被代理类的运行时类),就相当于可以获取被代理类的全部信息
              • Callback callback是用于提供增强代码的,一般都是写一个接口的实现,通常情况下都是匿名内部类,这里我们一般不适用Callback接口,而是使用它的子接口MethodInterceptor的实现类。

                生成代理类的工厂类 ProxyFactory:

                package com.tong;
                import com.oracle.jrockit.jfr.Producer;
                import net.sf.cglib.proxy.Enhancer;
                import net.sf.cglib.proxy.MethodInterceptor;
                import net.sf.cglib.proxy.MethodProxy;
                import java.lang.reflect.InvocationTargetException;
                import java.lang.reflect.Method;
                import java.util.Arrays;
                /**
                 * @author tong
                 * @Description:生成代理类的工厂
                 */
                public class ProxyFactory {
                    private Object target;
                    public ProxyFactory(Object target) {
                        this.target = target;
                    }
                    // 通过该方法可以生成任意目标类所对应的代理类
                    public static Object getProxy(Object target) {
                        // proxy就是我们创建的代理对象,这个对象可以执行被代理类中所有的方法,并且我们可以在代理对象中对被代理类的方法进行增强
                        Object proxy = Enhancer.create(target.getClass(), new MethodInterceptor() {
                            /**
                             * 这一步是整个过程的关键,代理类的实现要通过Enhancer类,我们需要通过Enhancer类中的create方法创建一个代理对象
                             * @param o 是一个代理对象的引用 (即:增强对象)
                             * @param method 是当前执行,即被拦截的被代理类方法
                             * @param objects 是当前执行方法所用的参数,索引顺序即为方法定义时参数的顺序
                             * @param methodProxy 指的是当前执行的方法的代理对象
                             * @return 通过反射调用method对象所表示的方法, 并获取该方法的返回值
                             */
                            @Override
                            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) {
                                Object result = null;
                                try {
                                    // 提供增强代码
                                    System.out.println("[动态代理][日志] " + method.getName() + ",参数:" + Arrays.toString(objects));
                                    //通过反射调用method对象所表示的方法,并获取该方法的返回值
                                    //在具有指定参数的指定对象上调用此method对象表示的底层方法。
                                    //此处就是通过target来确定调用的是具体哪个类中的方法
                                    result = method.invoke(target, objects);
                                    System.out.println("[动态代理][日志] " + method.getName() + ",结 果:" + result);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                    System.out.println("[动态代理][日志] " + method.getName() + ",异常:" + e.getMessage());
                                } finally {
                                    System.out.println("[动态代理][日志] " + method.getName() + ",方法执行完毕");
                                }
                                return result;
                            }
                        });
                        // 返回代理对象
                        return proxy;
                    }
                }
                

                测试类 ProxyTest:

                package com.tong;
                import org.junit.Test;
                /**
                 * @author tong
                 * @Description:测试类
                 */
                public class ProxyTest {
                    @Test
                    public void test(){
                        Star star = new Star();
                        /* proxy就是我们创建的代理对象,这个对象可以执行被代理类中所有的方法,
                        并且我们可以在代理对象中对被代理类的方法进行增强,
                        注意这里使用了强转,因为getProxy方法的返回值是Object类型的对象*/
                        Star proxy = (Star)ProxyFactory.getProxy(star);
                        proxy.sing();
                        System.out.println("-----------------分割线------------------");
                        proxy.dance();
                        System.out.println("-----------------分割线------------------");
                        // 创建被代理类的对象
                        ChineseCartoon chineseCartoon = new ChineseCartoon();
                        // 获取代理对象
                        ChineseCartoon proxy1 = (ChineseCartoon)ProxyFactory.getProxy(chineseCartoon);
                        proxy1.person("青莲地心火","陨落心炎");
                    }
                }
                

                运行结果:

                通俗易懂 快速理解 JDK动态代理 和 cglib动态代理,在这里插入图片描述,第2张

                结论:

                当测试类中通过父类的引用proxy调用了方法proxy.sing()时,由于父类 Star类 被 子类(动态代理类 给继承了【即:代理子类已经重写了父类中的sing()】。所以,当使用父类引用proxy调用sing()方法时,实际执行的是动态代理类 (子类) 中的 sing()方法。而在这个动态代理类重写的方法中,又会去调用 MethodInterceptor接口的匿名实现类中重写的 intercept() 方法对父类方法的调用进行拦截【即:在子类中采用方法拦截的技术拦截父类所有的方法调用】。在这个方法中,可以实现对被代理方法的功能增强【即:织入横切逻辑】,最后通过method.invoke()反射技术来调用父类中的方法。

                ❤ 由于我们的代理类工厂中有参构造的参数是Object类型的,所以最终实现的效果是动态生成代理类对象。

                tips:父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,父类引用是无法调用的。

                MethodInterceptor接口的匿名实现类中重写的 intercept() 方法的官方文档解释:

                通俗易懂 快速理解 JDK动态代理 和 cglib动态代理,在这里插入图片描述,第3张

                JDK动态代理

                代码:

                接口 Calculator :

                package com.tong.spring.calculator;
                /**
                 * @author tong
                 */
                public interface Calculator {
                    int add(int i, int j);
                    int sub(int i, int j);
                    int mul(int i, int j);
                    int div(int i, int j);
                }
                

                接口实现类 CalculatorImpl:

                package com.tong.spring.calculator;
                /**
                 * @author tong
                 */
                public class CalculatorImpl implements Calculator{
                    @Override
                    public int add(int i, int j) {
                        int result = i + j;
                        System.out.println("方法内部 result = " + result);
                        return result;
                    }
                    @Override
                    public int sub(int i, int j) {
                        int result = i - j;
                        System.out.println("方法内部 result = " + result);
                        return result;
                    }
                    @Override
                    public int mul(int i, int j) {
                        int result = i * j;
                        System.out.println("方法内部 result = " + result);
                        return result;
                    }
                    @Override
                    public int div(int i, int j) {
                        int result = i / j;
                        System.out.println("方法内部 result = " + result);
                        return result;
                    }
                }
                

                生产代理对象的工厂类 ProxyFactory:

                /**
                 * 动态代理有两种:
                 * 1. jdk动态代理,要求必须有接口,最终生成的代理类和目标类实现相同的接口,在com.sun.proxy包下,类名为$proxy+数字 (例如:$proxy6)
                 * 2. cglib动态代理,最终生成的代理类会继承目标类,并且和目标类在相同的包下
                 */
                public class ProxyFactory {
                    private Object target;
                    public ProxyFactory(Object target) {
                        this.target = target;
                    }
                    // 通过该方法可以生成任意目标类所对应的代理类
                    public Object getProxy(){
                        /**
                         * newProxyInstance():创建一个代理实例
                         * 其中有三个参数:
                         * 1、classLoader:指定加载动态生成的代理类的类加载器(注:所有引入的第三方类库以及自己编写的java类 都是由 应用类加载器 负责加载的)
                         【根类加载器(Bootstrap)> 扩展类加载器(Extension)> 系统类加载器(System)
                         系统类加载器又称为应用类加载器】
                         * 2、interfaces:获取目标对象实现的所有接口的class对象所组成的数组
                         * 3、invocationHandler:设置代理对象实现目标对象的接口的方法的过程,即代理类中如何重写接口中的抽象方法
                         */
                        //第一个参数,获取代理对象的类加载器 (类加载器是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载。所以,此处通过代理类或者被代理类获取到的类加载器都是同一个,或通过任何一个类获取到的类加载器都是同一个。)
                        ClassLoader classLoader = this.getClass().getClassLoader();
                        //第二个参数,被代理对象实现的所有接口数组
                        Class[] interfaces = target.getClass().getInterfaces();
                        //当通过代理类的对象发起对被重写的方法的调用时,都会转换为对如下的invoke方法的调用
                        //代理实例的调用处理程序
                        //第三个参数InvocationHandler的实现类,这里用了匿名内部类的方式
                        InvocationHandler invocationHandler = new InvocationHandler() {
                             //重写InvocationHandler的invoke方法,他有三个参数可以供我们使用
                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args){
                                /**
                                 * proxy:表示代理对象
                                 * method:表示要执行的方法(代理对象需要实现的抽象方法,即其中需要重写的 和目标类同名的方法)
                                 * args:表示要执行的方法的参数列表(method所对应方法的参数列表)
                                 */
                                Object result = null;
                                try {
                                    //method.getName(): 返回此method对象表示的方法的名称,作为字符串
                                    System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
                                     //通过反射调用method对象所表示的方法,并获取该方法的返回值
                                     //在具有指定参数的指定对象上调用此method对象表示的底层方法。
                                     //此处就是通过target来确定调用的是具体哪个类中的方法
                                     result = method.invoke(target, args);
                                     System.out.println("[动态代理][日志] "+method.getName()+",结 果:"+ result);
                                  } catch (Exception e) {
                                   e.printStackTrace();
                                   System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());
                                  } finally {
                                      System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");
                                  }
                                  return result;
                               }   
                            }; 
                        //返回指定接口的代理类的实例,该实例将方法调用分派给指定的调用处理程序。
                        return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
                      }
                }                                     
                

                测试类 ProxyTest:

                package com.tong.proxy;
                import com.tong.spring.calculator.Calculator;
                import com.tong.spring.calculator.CalculatorImpl;
                import com.tong.spring.calculator.proxy.ProxyFactory;
                import org.junit.Test;
                /**
                 * @author tong
                 */
                public class ProxyTest {
                    @Test
                    public void testDynamicProxy(){
                        ProxyFactory factory = new ProxyFactory(new CalculatorImpl());
                        Calculator proxy = (Calculator) factory.getProxy();
                        //创建好了代理对象,代理对象就可以执行被代理类实现的接口的方法;当通过代理类的对象发起对被重写的方法的调用时,都会转换为对调用处理器实现类中的invoke方法的调用,invoke方法中就可以对被代理类进行功能增强.
                        proxy.add(1, 5);
                //        proxy.div(1,0);
                    }
                }
                

                运行结果:

                通俗易懂 快速理解 JDK动态代理 和 cglib动态代理,在这里插入图片描述,第4张

                结论:

                通过factory.getProxy()创建好了代理对象后,代理对象proxy就可以执行被代理类实现的接口的方法;当通过代理类的对象发起对被重写的方法的调用时,都会转换为对调用处理器实现类中的invoke方法的调用,invoke方法中就可以对被代理类进行功能增强并通过反射调用被代理的同名方法。

                编写不易,有帮到各位朋友理解的,点个赞再走哦!๑(≥▽≤)๑