日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

CGLib动态代理原理

發布時間:2025/3/12 编程问答 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 CGLib动态代理原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

CGLib動態代理原理

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{@Overridepublic 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

EnhancerByCGLIBEnhancerByCGLIB

a063bd58.class),名字略長~后面會仔細介紹什么是FastClass,這里簡單說一下,就是給每個方法編號,通過編號找到方法,這樣可以避免頻繁使用反射導致效率比較低,也可以叫做FastClass機制
  然后我們可以結合生成的動態代理類來簡單看看原理,一個反編譯工具

我們就打開xxx.java文件,稍微進行整理一下,我們可以看到對于eat方法,在這個代理類中對應會有eat 和CGLIB$eat0這兩個方法;?其中前者eat則是我們使用代理類時候調用的方法,?后者CGLIB0這兩個方法;   - 其中前者eat 則是我們使用代理類時候調用的方法,   - 后者CGLIB0  ?eat使調  ?CGLIBeat0是在方法攔截器里面調用的,換句話來說當我們代碼調用代理對象的eat方法,然后會到方法攔截器中調用intercept方法,該方法內則通過proxy.invokeSuper調用CGLIB0是在方法攔截器里面調用的,  換句話來說當我們代碼調用代理對象的eat方法,然后會到方法攔截器中調用intercept方法,該方法內則通過proxy.invokeSuper調用CGLIB0調 調eat調interceptproxy.invokeSuper調CGLIBeat$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是否為空,也就是我們傳入的方法攔截器是否為空,如果不為空就最終到下面的_L4if(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;}}

根據上面的代碼我們可以知道代理類中主要有幾部分組成:

  • 重寫的父類方法,
  • CGLIB$eat$0這種奇怪的方法,
  • Interceptor()方法,
  • newInstance和get/setCallback方法
  • 3.FastClass機制分析

    為什么要用這種機制呢?直接用反射多好啊,但是我們知道反射雖然很好用,但是和直接new對象相比,效率有點慢,于是就有了這種機制, Jdk動態代理的攔截對象是通過反射的機制來調用被攔截方法的,反射的效率比較低,所以cglib采用了FastClass的機制來實現對被攔截方法的調用。FastClass機制就是對一個類的方法建立索引,通過索引來直接調用相應的方法,下面用一個小例子來說明一下,這樣比較直觀:

    public class test10 {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;}public int getIndex(String signature){switch(signature.hashCode()){case 3078479:return 1;case 3108270:return 2;}return -1;} }

    上例中,Test2是Test的Fastclass,在Test2中有兩個方法getIndex和invoke。在getIndex方法中對Test的每個方法建立索引,并根據入參(方法名+方法的描述符)來返回相應的索引。Invoke根據指定的索引,以ol為入參調用對象O的方法。這樣就避免了反射調用,提高了效率。代理類(Target

    EnhancerByCGLIBEnhancerByCGLIB

    788444a0)中與生成Fastclass相關的代碼如下:

    Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0"); localClass2 = Class.forName("net.sf.cglib.test.Target"); CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0");

    MethodProxy中會對localClass1和localClass2進行分析并生成FastClass,然后再使用getIndex來獲取方法g 和 CGLIB$g$0的索引,具體的生成過程將在后續進行介紹,這里介紹一個關鍵的內部類:

    private static class FastClassInfo{FastClass f1; // net.sf.cglib.test.Target的fastclassFastClass f2; // Target$$EnhancerByCGLIB$$788444a0 的fastclassint i1; //方法g在f1中的索引int i2; //方法CGLIB$g$0在f2中的索引}

    MethodProxy 中invokeSuper方法的代碼如下:

    FastClassInfo fci = fastClassInfo;return fci.f2.invoke(fci.i2, obj, args);

    當調用invokeSuper方法時,實際上是調用代理類的CGLIB$g0方法,CGLIB0方法,CGLIB0CGLIBg$0直接調用了目標類的g方法。所以,在第一節示例代碼中我們使用invokeSuper方法來調用被攔截的目標類方法。

    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),其實就是調用CGLIBeateateat這個方法


     invoke方法是個抽象方法,我們反編譯一下代理類的FastClass(也就是生成的那三個字節碼文件名稱最長的那個)就可以看到,由于代碼比較長,就不復制了…

    5.總結

    CGLib動態代理是將繼承用到了極致
      這里隨便畫一個簡單的圖看看整個過程,當我們去調用方法一的時候,在代理類中會先判斷是否實現了方法攔截的接口,沒實現的話直接調用目標類的方法一;如果實現了那就會被方法攔截器攔截,在方法攔截器中會對目標類中所有的方法建立索引,其實大概就是將每個方法的引用保存在數組中,我們就可以根據數組的下標直接調用方法,而不是用反射;索引建立完成之后,方法攔截器內部就會調用invoke方法(這個方法在生成的FastClass中實現),在invoke方法內就是調用CGLIB方 法 一 方法一方法一這種方法,也就是調用對應的目標類的方法一;

    一般我們要添加自己的邏輯就是在方法攔截器那里。。。。

    學習參考記錄:
    https://www.cnblogs.com/wyq1995/p/10945034.html

    總結

    以上是生活随笔為你收集整理的CGLib动态代理原理的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。