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$方法一$这种方法,也就是调用对应的目标类的方法一;

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

     

      

  • 相关阅读:
    react ts axios 配置跨域
    npm run eject“Remove untracked files, stash or commit any changes, and try again.”错误
    java 进程的参数和list的线程安全
    帆软报表 大屏列表跑马灯效果JS
    帆软报表 快速复用数据集,避免重复劳动
    分析云 OA中部门分级思路和实现方法
    分析云 分段器 只显示一个块的数据
    分析云 更改服务默认的端口号
    分析云U8项目配置方法新版本(2)
    Oracle 创建时间维度表并更新是否工作日字段
  • 原文地址:https://www.cnblogs.com/wyq1995/p/10945034.html
Copyright © 2011-2022 走看看