当前位置: 移动技术网 > IT编程>开发语言>Java > CGLib动态代理

CGLib动态代理

2019年06月03日  | 移动技术网IT编程  | 我要评论

好牌子网onkoo,神的重生,忍者千代火舞

  上一篇我们说过了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: // '\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;
    }

    

   
}

   根据上面的代码我们可以知道代理类中主要有几部分组成: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$方法一$这种方法,也就是调用对应的目标类的方法一;

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

 

 

  

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网