上一篇我們說過了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$方法一$這種方法,也就是調用對應的目標類的方法一;
一般我們要添加自己的邏輯就是在方法攔截器那里。。。。