CGLib動態代理

  上一篇咱們說過了jdk動態代理,這一篇咱們來看看CgLib動態代理,原本覺得CGLib動態代理和JDK實現的方式差很少的,可是仔細瞭解一下以後仍是有很大的差別的,這裏咱們先簡單說一下這兩種代理方式最大的區別,JDK動態代理是基於接口的方式,換句話來講就是代理類和目標類都實現同一個接口,那麼代理類和目標類的方法名就同樣了,這種方式上一篇說過了;CGLib動態代理是代理類去繼承目標類,而後重寫其中目標類的方法啊,這樣也能夠保證代理類擁有目標類的同名方法;html

  看一下CGLib的基本結構,下圖所示,代理類去繼承目標類,每次調用代理類的方法都會被方法攔截器攔截,在攔截器中才是調用目標類的該方法的邏輯,結構仍是一目瞭然的;java

 

1.CGLib的基本使用數組

    使用一下CGLib,在JDK動態代理中提供一個Proxy類來建立代理類,而在CGLib動態代理中也提供了一個相似的類Enhancer;maven

  使用的CGLib版本是2.2.2,我是隨便找的,不一樣的版本有點小差別,建議用3.x版本的.....我用的maven項目進行測試的,首先要導入cglib的依賴ide

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

 

  測試結果:this

  使用起來仍是很容易的,可是其中有不少小細節咱們要注意,下面咱們就慢慢的看;spa

 

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

    

   
}
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$方法一$這種方法,也就是調用對應的目標類的方法一;

  通常咱們要添加本身的邏輯就是在方法攔截器那裏。。。。

相關文章
相關標籤/搜索