zoukankan      html  css  js  c++  java
  • CGLib动态代理

      上一篇我们说过了jdk动态代理,这一篇我们来看看CgLib动态代理,本来以为CGLib动态代理和JDK实现的方式差不多的,但是仔细了解一下之后还是有很大的差异的,这里我们先简单说一下这两种代理方式最大的区别,JDK动态代理是基于接口的方式,换句话来说就是代理类和目标类都实现同一个接口,那么代理类和目标类的方法名就一样了,这种方式上一篇说过了;CGLib动态代理是代理类去继承目标类,然后重写其中目标类的方法啊,这样也可以保证代理类拥有目标类的同名方法;

      看一下CGLib的基本结构,下图所示,代理类去继承目标类,每次调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类的该方法的逻辑,结构还是一目了然的;

    1.CGLib的基本使用

        使用一下CGLib,在JDK动态代理中提供一个Proxy类来创建代理类,而在CGLib动态代理中也提供了一个类似的类Enhancer;

      使用的CGLib版本是2.2.2,我是随便找的,不同的版本有点小差异,建议用3.x版本的.....我用的maven项目进行测试的,首先要导入cglib的依赖

    <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
    </dependency>

      

      目标类(一个公开方法,另外一个用final修饰):

    package com.wyq.day527;
    
    public class Dog{
        
        final public void run(String name) {
            System.out.println("狗"+name+"----run");
        }
        
        public void eat() {
            System.out.println("狗----eat");
        }
    }

      方法拦截器:

    package com.wyq.day527;
    
    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class MyMethodInterceptor implements MethodInterceptor{
    
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("这里是对目标类进行增强!!!");
            //注意这里的方法调用,不是用反射哦!!!
            Object object = proxy.invokeSuper(obj, args);
            return object;
        }  
    }

      测试类:

    package com.wyq.day527;
    
    import net.sf.cglib.core.DebuggingClassWriter;
    import net.sf.cglib.proxy.Enhancer;
    
    public class CgLibProxy {
        public static void main(String[] args) {
            //在指定目录下生成动态代理类,我们可以反编译看一下里面到底是一些什么东西
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\java\java_workapace");
            
            //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
            Enhancer enhancer = new Enhancer();
            //设置目标类的字节码文件
            enhancer.setSuperclass(Dog.class);
            //设置回调函数
            enhancer.setCallback(new MyMethodInterceptor());
            
            //这里的creat方法就是正式创建代理类
            Dog proxyDog = (Dog)enhancer.create();
            //调用代理类的eat方法
            proxyDog.eat();       
        }
    }

      测试结果:

      使用起来还是很容易的,但是其中有很多小细节我们要注意,下面我们就慢慢的看;

    2.生成动态代理类

      首先到我们指定的目录下面看一下生成的字节码文件,有三个,一个是代理类的FastClass,一个是代理类,一个是目标类的FastClass,我们看看代理类(Dog$$EnhancerByCGLIB$$a063bd58.class),名字略长~后面会仔细介绍什么是FastClass,这里简单说一下,就是给每个方法编号,通过编号找到方法,这样可以避免频繁使用反射导致效率比较低,也可以叫做FastClass机制

      然后我们可以结合生成的动态代理类来简单看看原理,上一篇说过一个反编译工具jdGUI,但是貌似反编译这个字节码文件会出问题,我们可以用另外一个反编译工具jad,这个用起来不怎么直接。。。。百度云链接:https://pan.baidu.com/s/1tDxNWlA_0Ax1JXON10U_Pg  提取码:0zqv 

      我简单说说用法:1.必须将要反编译的字节码文件放到jad目录下;2.在jad目录下shift+鼠标右键,选择“在此处打开命令窗口”,也就是打开cmd;3.在黑窗口中输入jad -sjava Dog$$EnhancerByCGLIB$$a063bd58.class,就是就会以xxx.java的形式输出;如果输入jad -stxt Dog$$EnhancerByCGLIB$$a063bd58.class,就会以xxx.txt的形式输出,看你喜欢把字节码文件反编译成什么类型的。。。

      我们就打开xxx.java文件,稍微进行整理一下,我们可以看到对于eat方法,在这个代理类中对应会有eat 和CGLIB$eat$0这两个方法;其中前者则是我们使用代理类时候调用的方法,后者是在方法拦截器里面调用的,换句话来说当我们代码调用代理对象的eat方法,然后会到方法拦截器中调用intercept方法,该方法内则通过proxy.invokeSuper调用CGLIB$eat$0这个方法,不要因为方法名字太长了就觉得难,其实原理很简单。。。(顺便一提,不知道大家有没有发现代理类中只有eat方法,没有run方法,因为run方法被final修饰了,不可被重写,所以代理类中就没有run方法,这里要符合java规范!!!)

    package com.wyq.day527;
    
    import java.lang.reflect.Method;
    import net.sf.cglib.core.ReflectUtils;
    import net.sf.cglib.core.Signature;
    import net.sf.cglib.proxy.*;
    
    //可以看到这个代理类是继承我们的目标类Dog,并且顺便实现了一个Factory接口,这个接口就是一些设置回调函数和返回实例化对象的方法
    public class Dog$$EnhancerByCGLIB$$fbca2ec6 extends Dog implements Factory{
        //这里有很多的属性,仔细看一下就是一个方法对应两个,一个是Method类型,一个是MethodProxy类型
        private boolean CGLIB$BOUND;
        private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
        private static final Callback CGLIB$STATIC_CALLBACKS[];
        private MethodInterceptor CGLIB$CALLBACK_0;
        private static final Method CGLIB$eat$0$Method;
        private static final MethodProxy CGLIB$eat$0$Proxy;
        private static final Object CGLIB$emptyArgs[];
        private static final Method CGLIB$finalize$1$Method;
        private static final MethodProxy CGLIB$finalize$1$Proxy;
        private static final Method CGLIB$equals$2$Method;
        private static final MethodProxy CGLIB$equals$2$Proxy;
        private static final Method CGLIB$toString$3$Method;
        private static final MethodProxy CGLIB$toString$3$Proxy;
        private static final Method CGLIB$hashCode$4$Method;
        private static final MethodProxy CGLIB$hashCode$4$Proxy;
        private static final Method CGLIB$clone$5$Method;
        private static final MethodProxy CGLIB$clone$5$Proxy;
      
        //静态代码块,调用下面静态方法,这个静态方法大概做的就是获取目标方法中每个方法的MethodProxy对象
          static {
              CGLIB$STATICHOOK1();
          }
        
        //无参构造器
        public Dog$$EnhancerByCGLIB$$fbca2ec6()
        {
            CGLIB$BIND_CALLBACKS(this);
        }
    
        //此方法在上面的静态代码块中被调用
        static void CGLIB$STATICHOOK1(){
            //注意下面这两个Method数组,用于保存反射获取的Method对象,避免每次都用反射去获取Method对象
            Method[] amethod;
            Method[] amethod1;
            CGLIB$THREAD_CALLBACKS = new ThreadLocal();
            CGLIB$emptyArgs = new Object[0];
            
            //获取目标类的字节码文件
            Class class1 = Class.forName("com.wyq.day527.Dog$$EnhancerByCGLIB$$fbca2ec6");
            
            //代理类的字节码文件
            Class class2;
            
            //ReflectUtils是一个包装各种反射操作的工具类,通过这个工具类来获取各个方法的Method对象,然后保存到上述的Method数组中
            amethod = ReflectUtils.findMethods(new String[] {
                "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"
            }, (class2 = Class.forName("java.lang.Object")).getDeclaredMethods());
            Method[] _tmp = amethod;
            
            //为目标类的每一个方法都建立索引,可以想象成记录下来目标类中所有方法的地址,需要用调用目标类方法的时候根据地址就能直接找到该方法
            //这就是此处CGLIB$xxxxxx$$Proxy的作用。。。
            CGLIB$finalize$1$Method = amethod[0];
            CGLIB$finalize$1$Proxy = MethodProxy.create(class2, class1, "()V", "finalize", "CGLIB$finalize$1");
            CGLIB$equals$2$Method = amethod[1];
            CGLIB$equals$2$Proxy = MethodProxy.create(class2, class1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
            CGLIB$toString$3$Method = amethod[2];
            CGLIB$toString$3$Proxy = MethodProxy.create(class2, class1, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
            CGLIB$hashCode$4$Method = amethod[3];
            CGLIB$hashCode$4$Proxy = MethodProxy.create(class2, class1, "()I", "hashCode", "CGLIB$hashCode$4");
            CGLIB$clone$5$Method = amethod[4];
            CGLIB$clone$5$Proxy = MethodProxy.create(class2, class1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
            amethod1 = ReflectUtils.findMethods(new String[] {
                "eat", "()V"
            }, (class2 = Class.forName("com.wyq.day527.Dog")).getDeclaredMethods());
            Method[] _tmp1 = amethod1;
            CGLIB$eat$0$Method = amethod1[0];
            CGLIB$eat$0$Proxy = MethodProxy.create(class2, class1, "()V", "eat", "CGLIB$eat$0");
        }
    
        //这个方法就是调用目标类的的eat方法
        final void CGLIB$eat$0()
        {
            super.eat();
        }
    
        //这个方法是我们是我们要调用的,在前面的例子中调用代理对象的eat方法就会到这个方法中
        public final void eat(){
            //CGLIB$CALLBACK_0 = (MethodInterceptor)callback;
            CGLIB$CALLBACK_0;
            //这里就是判断CGLIB$CALLBACK_0是否为空,也就是我们传入的方法拦截器是否为空,如果不为空就最终到下面的_L4
            if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
    _L1:
            JVM INSTR pop ;
            CGLIB$BIND_CALLBACKS(this);
            CGLIB$CALLBACK_0;
    _L2:
            JVM INSTR dup ;
            JVM INSTR ifnull 37;
               goto _L3 _L4
    _L3:
            break MISSING_BLOCK_LABEL_21;
    _L4:
            break MISSING_BLOCK_LABEL_37;
            this;
            CGLIB$eat$0$Method;
            CGLIB$emptyArgs;
            CGLIB$eat$0$Proxy;
            //这里就是调用方法拦截器的intecept()方法
            intercept();
            return;
            super.eat();
            return;
        }
        
        //这里省略finalize,equals,toString,hashCode,clone,因为和上面的eat的两个方法差不多
        //..........
        //...........
        //..........
    
        public static MethodProxy CGLIB$findMethodProxy(Signature signature)
        {
            String s = signature.toString();
            s;
            s.hashCode();
            JVM INSTR lookupswitch 6: default 140
        //                   -1574182249: 68
        //                   -1310345955: 80
        //                   -508378822: 92
        //                   1826985398: 104
        //                   1913648695: 116
        //                   1984935277: 128;
               goto _L1 _L2 _L3 _L4 _L5 _L6 _L7
    _L2:
            "finalize()V";
            equals();
            JVM INSTR ifeq 141;
               goto _L8 _L9
    _L9:
            break MISSING_BLOCK_LABEL_141;
    _L8:
            return CGLIB$finalize$1$Proxy;
    _L3:
            "eat()V";
            equals();
            JVM INSTR ifeq 141;
               goto _L10 _L11
    _L11:
            break MISSING_BLOCK_LABEL_141;
    _L10:
            return CGLIB$eat$0$Proxy;
    _L4:
            "clone()Ljava/lang/Object;";
            equals();
            JVM INSTR ifeq 141;
               goto _L12 _L13
    _L13:
            break MISSING_BLOCK_LABEL_141;
    _L12:
            return CGLIB$clone$5$Proxy;
    _L5:
            "equals(Ljava/lang/Object;)Z";
            equals();
            JVM INSTR ifeq 141;
               goto _L14 _L15
    _L15:
            break MISSING_BLOCK_LABEL_141;
    _L14:
            return CGLIB$equals$2$Proxy;
    _L6:
            "toString()Ljava/lang/String;";
            equals();
            JVM INSTR ifeq 141;
               goto _L16 _L17
    _L17:
            break MISSING_BLOCK_LABEL_141;
    _L16:
            return CGLIB$toString$3$Proxy;
    _L7:
            "hashCode()I";
            equals();
            JVM INSTR ifeq 141;
               goto _L18 _L19
    _L19:
            break MISSING_BLOCK_LABEL_141;
    _L18:
            return CGLIB$hashCode$4$Proxy;
    _L1:
            JVM INSTR pop ;
            return null;
        }
    
        public static void CGLIB$SET_THREAD_CALLBACKS(Callback acallback[])
        {
            CGLIB$THREAD_CALLBACKS.set(acallback);
        }
    
        public static void CGLIB$SET_STATIC_CALLBACKS(Callback acallback[])
        {
            CGLIB$STATIC_CALLBACKS = acallback;
        }
    
        private static final void CGLIB$BIND_CALLBACKS(Object obj)
        {
            Dog$$EnhancerByCGLIB$$fbca2ec6 dog$$enhancerbycglib$$fbca2ec6 = (Dog$$EnhancerByCGLIB$$fbca2ec6)obj;
            if(dog$$enhancerbycglib$$fbca2ec6.CGLIB$BOUND) goto _L2; else goto _L1
    _L1:
            Object obj1;
            dog$$enhancerbycglib$$fbca2ec6.CGLIB$BOUND = true;
            obj1 = CGLIB$THREAD_CALLBACKS.get();
            obj1;
            if(obj1 != null) goto _L4; else goto _L3
    _L3:
            JVM INSTR pop ;
            CGLIB$STATIC_CALLBACKS;
            if(CGLIB$STATIC_CALLBACKS != null) goto _L4; else goto _L5
    _L5:
            JVM INSTR pop ;
              goto _L2
    _L4:
            (Callback[]);
            dog$$enhancerbycglib$$fbca2ec6;
            JVM INSTR swap ;
            0;
            JVM INSTR aaload ;
            (MethodInterceptor);
            CGLIB$CALLBACK_0;
    _L2:
        }
    
        public Object newInstance(Callback acallback[])
        {
            CGLIB$SET_THREAD_CALLBACKS(acallback);
            CGLIB$SET_THREAD_CALLBACKS(null);
            return new Dog$$EnhancerByCGLIB$$fbca2ec6();
        }
    
        public Object newInstance(Callback callback)
        {
            CGLIB$SET_THREAD_CALLBACKS(new Callback[] {
                callback
            });
            CGLIB$SET_THREAD_CALLBACKS(null);
            return new Dog$$EnhancerByCGLIB$$fbca2ec6();
        }
    
        public Object newInstance(Class aclass[], Object aobj[], Callback acallback[])
        {
            CGLIB$SET_THREAD_CALLBACKS(acallback);
            JVM INSTR new #2   <Class Dog$$EnhancerByCGLIB$$fbca2ec6>;
            JVM INSTR dup ;
            aclass;
            aclass.length;
            JVM INSTR tableswitch 0 0: default 35
        //                   0 28;
               goto _L1 _L2
    _L2:
            JVM INSTR pop ;
            Dog$$EnhancerByCGLIB$$fbca2ec6();
              goto _L3
    _L1:
            JVM INSTR pop ;
            throw new IllegalArgumentException("Constructor not found");
    _L3:
            CGLIB$SET_THREAD_CALLBACKS(null);
            return;
        }
    
        public Callback getCallback(int i)
        {
            CGLIB$BIND_CALLBACKS(this);
            this;
            i;
            JVM INSTR tableswitch 0 0: default 30
        //                   0 24;
               goto _L1 _L2
    _L2:
            CGLIB$CALLBACK_0;
              goto _L3
    _L1:
            JVM INSTR pop ;
            null;
    _L3:
            return;
        }
    
        public void setCallback(int i, Callback callback)
        {
            switch(i)
            {
            case 0: // ''
                CGLIB$CALLBACK_0 = (MethodInterceptor)callback;
                break;
            }
        }
    
        public Callback[] getCallbacks()
        {
            CGLIB$BIND_CALLBACKS(this);
            this;
            return (new Callback[] {
                CGLIB$CALLBACK_0
            });
        }
    
        public void setCallbacks(Callback acallback[])
        {
            this;
            acallback;
            JVM INSTR dup2 ;
            0;
            JVM INSTR aaload ;
            (MethodInterceptor);
            CGLIB$CALLBACK_0;
        }
    
        
    
       
    }
    View Code

       根据上面的代码我们可以知道代理类中主要有几部分组成:1.重写的父类方法,2.CGLIB$eat$0这种奇怪的方法,3.Interceptor()方法,4.newInstance和get/setCallback方法

    3.FastClass机制分析

      为什么要用这种机制呢?直接用反射多好啊,但是我们知道反射虽然很好用,但是和直接new对象相比,效率有点慢,于是就有了这种机制,我参考这篇博客https://www.cnblogs.com/cruze/p/3865180.html,有个小例子可以说的非常清楚;

    public class test10 {
      //这里,tt可以看作目标对象,fc可以看作是代理对象;首先根据代理对象的getIndex方法获取目标方法的索引,
      //然后再调用代理对象的invoke方法就可以直接调用目标类的方法,避免了反射
    public static void main(String[] args){ Test tt = new Test(); Test2 fc = new Test2(); int index = fc.getIndex("f()V"); fc.invoke(index, tt, null); } } class Test{ public void f(){ System.out.println("f method"); } public void g(){ System.out.println("g method"); } } class Test2{ public Object invoke(int index, Object o, Object[] ol){ Test t = (Test) o; switch(index){ case 1: t.f(); return null; case 2: t.g(); return null; } return null; } //这个方法对Test类中的方法建立索引 public int getIndex(String signature){ switch(signature.hashCode()){ case 3078479: return 1; case 3108270: return 2; } return -1; } }

      在CGLib的代理类中,生成FastClass相关代码如下;

    Class class1 = Class.forName("com.wyq.day527.Dog$$EnhancerByCGLIB$$fbca2ec6");
    Class class2 = Class.forName("com.wyq.day527.Dog")).getDeclaredMethods()

    CGLIB$eat$0$Proxy = MethodProxy.create(class2, class1, "()V", "eat", "CGLIB$eat$0");

    4.简单原理

      上面我们看了CGLib动态代理的用法、实际生成的代理类以及FastClass机制,下面我们就以最前面的那个例子中调用eat()方法来看看主要的调用步骤;

      第一步:是经过一系列操作实例化出了Enhance对象,并设置了所需要的参数然后enhancer.create()成功创建出来了代理对象,这个就不多说了...

      第二步:调用代理对象的eat()方法,会进入到方法拦截器的intercept()方法,在这个方法中会调用proxy.invokeSuper(obj, args);方法

      第三步:invokeSuper中,通过FastClass机制调用目标类的方法

      方法拦截器中只有一个invoke方法,这个方法有四个参数,obj表示代理对象,method表示目标类中的方法,args表示方法参数,proxy表示代理方法的MethodProxy对象

      在这个方法内部会调用proxy.invokeSuper(obj, args)方法,我们进入.invokeSuper方法内部看看:

      简单看看init()方法:

      FastClassInfo内部如下图,由此可以看出prxy.invokeSuper()方法中fci.f2.invoke(fci.i2, obj, args),其实就是调用CGLIB$eat$这个方法

     

       invoke方法是个抽象方法,我们反编译一下代理类的FastClass(也就是生成的那三个字节码文件名称最长的那个)就可以看到,由于代码比较长,就不复制了...

     

    5.总结

      CGLib动态代理是将继承用到了极致,我们这里也就是简单的看了看,没有怎么深入,想深入了解的可以自己查查资料。。。感觉暂时到这里就差不多了,以后用到的话再继续挖掘!对于一个新的东西,不要想着一下子全部弄懂,因为太吃力了,一口吃不成胖子,要先弄懂一点,然后慢慢深入即可!

      这里随便画一个简单的图看看整个过程,当我们去调用方法一的时候,在代理类中会先判断是否实现了方法拦截的接口,没实现的话直接调用目标类的方法一;如果实现了那就会被方法拦截器拦截,在方法拦截器中会对目标类中所有的方法建立索引,其实大概就是将每个方法的引用保存在数组中,我们就可以根据数组的下标直接调用方法,而不是用反射;索引建立完成之后,方法拦截器内部就会调用invoke方法(这个方法在生成的FastClass中实现),在invoke方法内就是调用CGLIB$方法一$这种方法,也就是调用对应的目标类的方法一;

      一般我们要添加自己的逻辑就是在方法拦截器那里。。。。

     

      

  • 相关阅读:
    JS 知识点补充
    JS 数据之间类型的转化
    JS 数据的类型
    数据结构--数组、单链表和双链表介绍 以及 双向链表
    数据结构--队列
    数据结构--栈
    24. 两两交换链表中的节点
    23. 合并K个排序链表
    22. 括号生成
    21. 合并两个有序链表
  • 原文地址:https://www.cnblogs.com/wyq1995/p/10945034.html
Copyright © 2011-2022 走看看