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方法,然后會到方法攔截器中調用intercept方法,該方法內則通過proxy.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;}}根據上面的代碼我們可以知道代理類中主要有幾部分組成:
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方法,CGLIB0方法,CGLIBg$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动态代理原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ICML论文录取难度逐年上升,New I
- 下一篇: 第四范式陈雨强获评首届世界人工智能大会云