案例分析數組
package com.demo; public class UserService { public void addUser(){ System.out.println("添加用戶"); } }
簡單介紹下UserService
模擬數據層操做,TxHelper
做爲一個cglib加強的回調.緩存
package com.demo; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class TxHelper implements MethodInterceptor { public Object intercept(final Object obj, final Method method, final Object[] args, final MethodProxy proxy) throws Throwable { System.out.println("開啓事務"); Object res=proxy.invokeSuper(obj,args); // 這裏調用invoke方法就會致使死循環從而棧溢出 // Object res=proxy.invoke(obj,args); System.out.println("關閉事務"); return res; } /*這個方法根據clazz使用空參構造器獲取clazz的cglib子類*/ public Object getInstance(Class clazz){ Enhancer enhancer=new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } public Object getInstance2(Class clazz,Class params[],Object[] args){ Enhancer enhancer=new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(params,args); } }
TxHelper
中爲了節省代碼量,將獲取Cglib生成的子類寫在TxHelper
中,即getInstance(class)
和getInstance2(clazz,clazz[],Object[])
方法,都是調用的Enhancer.create
來獲取Cglib子類.安全
Cglib依賴添加工具
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>asm</groupId> <artifactId>asm-util</artifactId> <version>3.1</version> </dependency> </dependencies>
依賴說明:cglib2.2版本只依賴於asm3.1,asm-util是asm的相關工具包,這裏引入是另有目的.性能
測試方法測試
package com.demo; import net.sf.cglib.core.DebuggingClassWriter; public class UserServiceTests { public static void main(String[] args) { //設置cglib.debugLocation屬性指定將動態代理的類指定生成在哪裏 // 如下方式等價於 -Dcglib.debugLocation=E:\\data\\blog System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E:\\data\\blog"); UserService userService= (UserService) new TxHelper().getInstance(UserService.class); userService.addUser(); } }
測試方法說明: System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY)
是用於設置 cglib動態代理的子類生成的位置,等價於啓動參數 -Dcglib.debugLocation=E:\\data\\blog
,這樣就能夠將代理子類生成到咱們指定目錄。 上面額外引入的依賴 asm-util
則是會將動態生成的子類的字節碼展現出來。ui
public static final String DEBUG_LOCATION_PROPERTY = "cglib.debugLocation";
測試效果this
能夠看到methodProxy.invokeSuper
方法會調用父類的方法 成功添加父類,至於另外的狀況最後再分析.線程
進入以前設置的cglib.debugLocation
指定的目錄,查看cglib生成的子類class文件.
在該目錄下的本身的包名文件夾com/demo
下會有咱們的UserService
的cglib子類,額外的還有net/sf/cglib/core
以及net/sf/cglib/proxy
這兩個cglib額外生成的層級目錄,由於上面引入了 asm-util ,因此伴隨着class文件,還會有一些特殊的 asm 文件,asm 文件使用notepad++等工具就能夠查看了,這些asm文件記錄着每個類生成過程當中字節碼.
查看cglib生成的 class文件
能夠看到咱們的UserService
類就已經生成了三個class文件, 其中UserService$$EnhancerByCGLIB$$268385a2
能夠理解爲是 UserService
的真實子類,而 UserService$$FastClassByCGLIB$$417ebd8c
和UserService$$EnhancerByCGLIB$$268385a2$$FastClassByCGLIB$$c6a21d27
則是計算動態代理類調用方法走父類仍是自己的方法,這裏後面也會發現CGLIB比反射效率高(個人理解,直接調用會比反射效率高).
查看 asm 文件
補充一點:class version 46.0表明 JDK1.2版本編譯的class文件,高版本編譯環境可編譯低版本編譯過來的Class文件,可是低版本的JDK1.7就不能編譯JDK1.8的class文件了,這時候嘗試編譯會報錯:==Unsupported major version==
CGLIB子類一探究竟
上面測試效果、CGLIB子類咱們都獲取到了,甭管怎麼生成的CGLIB子類,咱們先來看看,CGLIB子類究竟長啥樣?
一般會用 jd-gui 工具查看class文件,可是cglib生成的子類用 jd-gui 查看效果不是很好,不利於閱讀,查看方式:直接將class文件拖到 IDEA 中,就能夠查看反編譯後的代碼.
我不太習慣看 反編譯後的 class 文件,因而就將 class文件的內容複製到 com/demo
目錄下,而且修改文件名爲 .java結尾,可是class文件內容直接複製到java 文件中,會有好多處紅叉報錯:一一解決下. 這裏改爲java文件只是爲了方便本身閱讀,不能用來調試.
第一處報錯: final類型變量可能沒初始化,致使編譯不過;其實cglib是初始化了,可是爲啥IDEA不認呢?
先說解決方法: 將final 關鍵字都去掉
其實cglib關於 final變量都是初始化了的,一個靜態代碼塊調用 CGLIB$STATICHOOK1
,在靜態方法CGLIB$STATICHOOK1
中進行了初始化,可是爲啥IDEA不認呢?
static { CGLIB$STATICHOOK1(); } static void CGLIB$STATICHOOK1() { CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; Class var0 = Class.forName("com.demo.UserService$$EnhancerByCGLIB$$268385a2"); Class var1; Method[] var10000 = ReflectUtils.findMethods(new String[]{"addUser", "()V", "getUser", "()V"}, (var1 = Class.forName("com.demo.UserService")).getDeclaredMethods()); CGLIB$addUser$0$Method = var10000[0]; CGLIB$addUser$0$Proxy = MethodProxy.create(var1, var0, "()V", "addUser", "CGLIB$addUser$0"); CGLIB$getUser$1$Method = var10000[1]; CGLIB$getUser$1$Proxy = MethodProxy.create(var1, var0, "()V", "getUser", "CGLIB$getUser$1"); var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods()); CGLIB$finalize$2$Method = var10000[0]; CGLIB$finalize$2$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$2"); CGLIB$equals$3$Method = var10000[1]; CGLIB$equals$3$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$3"); CGLIB$toString$4$Method = var10000[2]; CGLIB$toString$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$4"); CGLIB$hashCode$5$Method = var10000[3]; CGLIB$hashCode$5$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$5"); CGLIB$clone$6$Method = var10000[4]; CGLIB$clone$6$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$6"); }
第二處報錯:
MethodInterceptor.intercept
方法異常沒有捕獲解決方案: 手動Try catch異常吧.
報錯三:
new
實例化對象缺乏括號,下面多了字節碼<init>()
解決方案:上面添上括號,
<init>()
這行註釋掉
另外兩個 FastClass 類也是相似的操做,細心地發現:只有UserService$$EnhancerByCGLIB$$743464da
是繼承了咱們的UserService
類的而且實現了Factory
接口 ,另外兩個類都是繼承自FastClass
.
上一步已經獲得了cglib生成的動態代理類,動態代理類確定要初始化一個實例對象,實例化的流程呢:AbstractClassGenerator.create
--->Enhancer.firstInstance
--->Enhancer.createUsingReflection
private Object createUsingReflection(Class type) { setThreadCallbacks(type, callbacks); try{ if (argumentTypes != null) { return ReflectUtils.newInstance(type, argumentTypes, arguments); } else { return ReflectUtils.newInstance(type); } }finally{ // clear thread callbacks to allow them to be gc'd setThreadCallbacks(type, null); } }
Enhancer.createUsingReflection
首先setThreadCallbacks(type,callbacks)
----->ReflectUtils.newInstance
type就是當前這個動態代理子類的class UserService$$EnhancerByCGLIB$$743464da
,callbacks 就是咱們本身設置到enhancer
中的.
setThreadCallbacks
就是調用動態代理的類的靜態CGLIB$SET_THREAD_CALLBACKS
方法,將callbacks
設置進入,查看動態代理類的靜態CGLIB$SET_THREAD_CALLBACKS
方法:
public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) { CGLIB$THREAD_CALLBACKS.set(var0); } private static ThreadLocal CGLIB$THREAD_CALLBACKS;
其中 CGLIB$THREAD_CALLBACKS
原本是final static 變量,不過被咱們強行改成了static,這裏能夠看到 動態代理的類有個靜態量ThreadLocal,持有回調數組callback
,至於ThreadLocal 的初始化在 static 代碼塊中完成了.
ReflectUtils.newInstance
方法會根據動態代理類可用的構造方法調用 反射來實例化 動態代理子類UserService$$EnhancerByCGLIB$$743464da
. 而實例化完成以後,仍然調用CGLIB$THREAD_CALLBACKS
將ThreadLocal變量中的callback
清除.
UserService$$EnhancerByCGLIB$$743464da
的實例化
public UserService$$EnhancerByCGLIB$$743464da() { CGLIB$BIND_CALLBACKS(this); } private static final void CGLIB$BIND_CALLBACKS(Object var0) { UserService$$EnhancerByCGLIB$$743464da var1 = (UserService$$EnhancerByCGLIB$$743464da)var0; if (!var1.CGLIB$BOUND) { var1.CGLIB$BOUND = true; Object var10000 = CGLIB$THREAD_CALLBACKS.get(); if (var10000 == null) { var10000 = CGLIB$STATIC_CALLBACKS; if (CGLIB$STATIC_CALLBACKS == null) { return; } } var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0]; } }
能夠看到 動態代理類初始化調用了 CGLIB$BIND_CALLBACKS(this)
,先判斷CGLIB$BOUND標誌位,第一次綁定以後就爲true了,綁定過程就是 從 ThreadLocal 中提取出來callback
,並賦給動態代理對象的CGLIB$CALLBACK_0
屬性,回調屬性都是以CGLIB$CALLBACK_
開頭,假如回調數組元素爲多個,那就有多少個屬性,分別是CGLIB$CALLBACK_0
、CGLIB$CALLBACK_1
.
總結下來:回調數組callback賦值給了動態代理類的每個屬性,有幾個回調元素,就有幾個回調屬性;賦值過程是在實例化動態代理類時候完成的,爲了防止線程不安全,用的是ThreadLocal來保存callback
如今UserService$$EnhancerByCGLIB$$743464da
實例已經有了,調用addUser
方法,會先進入子類的方法.
public final void addUser() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { try { var10000.intercept(this, CGLIB$addUser$0$Method, CGLIB$emptyArgs, CGLIB$addUser$0$Proxy); } catch (Throwable throwable) { throwable.printStackTrace(); } } else { super.addUser(); } }
嘗試獲取CGLIB$CALLBACK_0
屬性,爲何說嘗試呢?若是CGLIB$CALLBACK_0
爲空,還回去從 ThreadLocal中取,當獲取到CGLIB$CALLBACK_0
不爲空,調用CGLIB$CALLBACK_0.intercept
方法.
四個入參:
CGLIB$addUser$0$Method
靜態代碼塊中初始化,獲取的是父類void UserService.addUser()
方法,也就是父類方法的Method.private final static Method CGLIB$addUser$0$Method; static{ .......... Class var1=null; try { CGLIB$addUser$0$Method = ReflectUtils.findMethods(new String[]{"addUser", "()V"}, (var1 = Class.forName("com.demo.UserService")).getDeclaredMethods())[0]; } catch (ClassNotFoundException e) { e.printStackTrace(); } ............ }
CGLIB$emptyArgs
一個大小爲0的Object數組
CGLIB$addUser$0$Proxy
經過MethodProxy.create(superClass,cglibSonClass,methodDescriptor,superMethodName,sonMethodName)
來獲取MethodProxy對象.
private static final MethodProxy CGLIB$addUser$0$Proxy; static{ Class var0 = null; try { var0 = Class.forName("com.demo.UserService$$EnhancerByCGLIB$$743464da"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Class var1=null; try { CGLIB$addUser$0$Method = ReflectUtils.findMethods(new String[]{"addUser", "()V"}, (var1 = Class.forName("com.demo.UserService")).getDeclaredMethods())[0]; } catch (ClassNotFoundException e) { e.printStackTrace(); } CGLIB$addUser$0$Proxy = MethodProxy.create(var1, var0, "()V", "addUser", "CGLIB$addUser$0"); }
MethodProxy.create過程
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) { MethodProxy proxy = new MethodProxy(); proxy.sig1 = new Signature(name1, desc); proxy.sig2 = new Signature(name2, desc); proxy.createInfo = new CreateInfo(c1, c2); return proxy; }
解釋說明: c1是父類class,c2是cglib子類class,desc就是方法修飾符,好比()V表明無如參void類型,(Ljava/lang/String;)I表明String入參int返回值的方法,對象類型都是 L全限定名; 這種,基本數據類型int對應 I 這種,數組對應 [
name1是父類中方法的名稱,name2是子類中方法的名稱 ,好比addUser
如今cglib子類想要直接調用父類的方法,不須要加強,那我調用CGLIB$addUser$0
方法就等價於 直接調用父類的方法,調用方式以下:
意味着:cglib子類中的addUser方法就是加強的方法,而CGLIB$addUser$0
就是未加強的方法,方法名的生成規則後續再記錄.
public static void main(String[] args) throws Exception { //設置cglib.debugLocation屬性指定將動態代理的類指定生成在哪裏 // 如下方式等價於 -Dcglib.debugLocation=E:\\data\\blog System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E:\\data\\blog"); UserService userService= (UserService) new TxHelper().getInstance(UserService.class); // userService.addUser(); for (final Method declaredMethod : userService.getClass().getDeclaredMethods()) { System.out.println(declaredMethod.getName()); } Method m = userService.getClass().getDeclaredMethod("CGLIB$addUser$0", null); m.invoke(userService,null); }
回到MethodProxy.create
中,生成了一個MethodProxy對象,sig一、sig2都是Signature
類型的,表明方法簽名,sig1是父類方法的簽名,sig2是子類方法的簽名;而 createInfo
屬性最主要的是持有 c1(父類class)、c2(cglib子類class).
至此咱們已經分析完畢MethodProxy.create
的邏輯.
MethodInterceptor.intercept
調用邏輯
MethodInterceptor 怎麼來的、有什麼屬性咱們已經分析完畢,intercept
方法就進入了咱們自定義的回調類中,離咱們分析的目標更近了.
咱們自定義回調邏輯中,知道proxy.invokeSuper
方法纔是正確的,而proxy.invoke
會致使棧溢出. 如今咱們已經知道這個intercept方法的幾個入參: obj 就是當前cglib子類實例,method就是父類UserService.adUser()
的Method,args就是方法的入參,proxy就是上面建立的MethodProxy
對象。 cglib子類中每個繼承父類的方法都會生成一個MethodProxy對應,額外還有Object類的toString
、hashCode
、equals
、finalize
、clone
方法
public Object intercept(final Object obj, final Method method, final Object[] args, final MethodProxy proxy) throws Throwable { System.out.println("開啓事務"); Object res=proxy.invokeSuper(obj,args); // 這裏invoke方法就會致使死循環從而棧溢出 // Object res=proxy.invoke(obj,args); System.out.println("關閉事務"); return res; }
MethodProxy.invokeSuper
MethodProxy的 invokeSuper方法以下.
說明文檔上介紹:調用加強類(cglib子類)的父類的方法,obj對象就是加強的cglib子類.
init()
方法private void init() { if (fastClassInfo == null) { synchronized (initLock) { if (fastClassInfo == null) { CreateInfo ci = createInfo; FastClassInfo fci = new FastClassInfo(); fci.f1 = helper(ci, ci.c1); fci.f2 = helper(ci, ci.c2); fci.i1 = fci.f1.getIndex(sig1); fci.i2 = fci.f2.getIndex(sig2); fastClassInfo = fci; } } } }
雙重檢查鎖機制,一直覺得是安全的,可是cglib註釋提到 這段代碼在JDK1.5以前可能會致使fastClassInfo
實例化多個?因此fastClassInfo
對象用了volatile
關鍵字來修飾,咱也沒用過volatile
關鍵字,這裏就暫留做疑問吧.
createInfo
對象以前提到了,是在實例化MethodProxy
中實例化的,持有兩個重要屬性父類c1、cglib子類c2,helper
方法另外生成了兩個FastClass
的實例,f1就是UserService
這種被UserService$$FastClassByCGLIB$$xxxx
,f2就是UserService$$EnhancerByCGLIB$$xxx$$FastClassByCGLIB$$xxx
,生成過程採用ASM生成字節碼較爲複雜就忽略,若是須要查看字節碼,引入asm-util
而且指定cglib.debugLocation
參數便可生成asm文件.這裏的fastClassInfo
是單例的,又有點緩存的意思,不少初始化邏輯在第一次實例化過程當中完成,後續進入就不須要init
.
init()
方法中volatile
修飾fastClassInfo
的做用是啥?
參考上面獲取cglib java類,咱們能夠看到指定的cglib.debugLocation
指定包目錄下另外兩個class
就是這裏的FastClass
以UserService
的FastClass來看,主要實現了三類方法getIndex
、invoke
、newInstance
,先來看第一類方法getIndex
Signature是cglib包中的類,表明一個方法的簽名. 根據Signature來獲取索引,索引表明要執行哪一個方法. 首先判斷 方法名 + 方法返回值 的hash值,hash值一致的狀況下,還須要再用 equals 方法再次比較。 由於兩個不一樣的字符串是有可能hash值相等的. 這樣確保獲取到的索引正確,索引用呢是在invoke
方法中. 至於這個索引則是在 生成字節碼中判斷的.
上面獲取的方法索引在這裏就能夠體現用處了,就是var1參數。 switch中的序號和上面 getIndex
必定是一一對應的.
同理,cglib子類的getIndex
、invoke
方法都是相似的,只不過cglib子類的 FastClassgetIndex以及 invoke的選項會多一倍,由於一個是 繼承自父類,也就是加強的方法,還一個是重命名的父類方法,單純調用父類方法,好比CGLIB$toString$3()
#### init方法繼續
if (fastClassInfo == null) { synchronized (initLock) { if (fastClassInfo == null) { CreateInfo ci = createInfo; FastClassInfo fci = new FastClassInfo(); fci.f1 = helper(ci, ci.c1); fci.f2 = helper(ci, ci.c2); fci.i1 = fci.f1.getIndex(sig1); fci.i2 = fci.f2.getIndex(sig2); fastClassInfo = fci; } } }
FastClassInfo
經過ASM生成了兩個FastClass子類以後,f1就是 UserService$$FastClassByCGLIB
的實例,f2就是UserService$$EnhancerByCGLIB$$FastClass..
的實例,sig1表明被加強方法的簽名,addUser()V
這種,sig2表明別加強方法在cglib子類中的簽名,如CGLIB$addUser$0
.
public Object invokeSuper(Object obj, Object[] args) throws Throwable { try { init(); FastClassInfo fci = fastClassInfo; return fci.f2.invoke(fci.i2, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } }
fci.i2就是CGLIB$addUser$0
在其中的索引,好比我這裏是15. obj對象是cglib的子類,invokeSuper就是調用這個cglib加強類的索引爲15的方法,而繞回來這個索引又是經過 getIndex
獲取CGLIB$addUser$0
獲取的,等於直接調用了 cglib加強子類的CGLIB$addUser$0
方法,就是調用了父類的addUser方法. 這樣invokeSuper就實現了動態調用父類的方法,好比UserService
有不少種方法,我如今是A
方法,那裏面的MethodProxy
對象又是一個全新的關聯了A
以及CGLIB$A$...
方法,而咱們只須要使用proxy.invokeSuper
就能自動調用父類方法,這裏繞開了反射,相應的在動態代理類、每一個方法第一次調用會帶來必定的性能損耗,可是後續使用起來與普通調用無差異,會優於後續反射來調用方法.
final void CGLIB$addUser$0() { super.addUser(); }
相信這裏咱們就能大概明白,爲啥invoke
會帶來方法死循環.
public Object invoke(Object obj, Object[] args) throws Throwable { try { init(); FastClassInfo fci = fastClassInfo; return fci.f1.invoke(fci.i1, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } catch (IllegalArgumentException e) { if (fastClassInfo.i1 < 0) throw new IllegalArgumentException("Protected method: " + sig1); throw e; } }
這裏與invokeSuper
的區別大概是,f2換成了f1,索引也換成了對應的i1,咱們不饒一圈,就是直接調用了動態代理類的addUser
方法. 嗯? 咱們都加強了addUser
方法,你還調用加強的方法,那不是又饒了一圈嘛,難怪會死循環,從而棧溢出.
我經常在想假如我就不想加強某個方法,咋調用呢? 想了個笨的方法,反射找到那個直接調用父類的方法,反射執行行不?
public static void main(String[] args) throws Exception { //設置cglib.debugLocation屬性指定將動態代理的類指定生成在哪裏 // 如下方式等價於 -Dcglib.debugLocation=E:\\data\\blog System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E:\\data\\blog"); UserService userService= (UserService) new TxHelper().getInstance(UserService.class); // userService.addUser(); for (final Method declaredMethod : userService.getClass().getDeclaredMethods()) { System.out.println(declaredMethod.getName()); } Method m = userService.getClass().getDeclaredMethod("CGLIB$addUser$0", null); m.setAccessible(true); m.invoke(userService,null); }
查看輸出.......
想了想,知識有限,咱們查看他全部的方法,還好對於CGLIB有一點點了解,找那個方法名相似CGLIB$XXX$數字
的方法,反射調用試試唄, 雖然這種方法確定存在不少弊端,奈何能耐有限呢?