Cglib 動態代理
本文的寫作目的是為了探究 Spring 框架中在使用@Transactional標注的方法中使用 this 進行自調用時事務失效的原因,各種視頻教程中只是簡單指出 this 指向的不是代理類對象,而是目標類對象,但是并沒有解釋為什么 this 不是代理類對象?
在學習完 JDK 動態代理之后,我認為是動態代理的原因。雖然知道 Cglib Proxy 和 JDK Proxy 的實現原理不同,但當時認為方法調用只能通過 invoke 進行反射調用(錯誤依據),而傳遞給 invoke 方法的對象就是目標類對象,因此 this 指向的就是傳遞過來的目標類對象。具體可以查看另一篇博客。
最近學習完 Cglib 動態代理之后,發現動態代理類進行方法調用并不是只能依靠反射調用的,因此那一篇博客的分析也就不成立了。先說結論,在 Cglib 動態代理中,由于繞開了反射調用方法,所以 this 既可以指向代理類對象,也可以和 JDK 動態代理一樣指向目標類對象,而 Spring 框架中選擇了后者,從而有了 this 造成事務失效的情況。但是就 Cglib 本身實現動態代理而言,這個問題是可以避免的。
依賴環境
下面兩個 pom 依賴二選一即可
<!--spring-core中包含cglib-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.3.30</version>
</dependency><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.1</version>
</dependency>
案例演示
public class CglibProxyTest {private static final String CLASSPATH = ClassLoader.getSystemResource("").getPath().substring(1);public static void main(String[] args) {// 設置生成字節碼文件System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, CLASSPATH);MethodInterceptor methodInterceptor = new MyMethodInterceptor();Enhancer enhancer = new Enhancer();// 設置父類字節碼enhancer.setSuperclass(ServiceImpl.class);// 設置增強方法(即方法攔截器)// TODO: 從代理類的源碼中可以看出來,setCallbacks只會使用到第一個攔截器,那么setCallbacks方法有什么意義呢?enhancer.setCallback(methodInterceptor);ServiceImpl serviceImpl = (ServiceImpl) enhancer.create();serviceImpl.show("Hello World");// 測試cglib代理方式下的this自調用和Spring事務的this自調用的區別//serviceImpl.getMsg(1, 2);}
}class MyMethodInterceptor implements MethodInterceptor {/*** 攔截方法** @param proxy 代理類對象* @param targetMethod 目標類中的方法對象* @param args 方法參數* @param methodProxy Cglib底層使用到的MethodProxy對象,并不是代理方法* @return* @throws Throwable*/@Overridepublic Object intercept(Object proxy, Method targetMethod, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("proxy.getClass() = " + proxy.getClass());System.out.println("targetMethod.getName() = " + targetMethod.getName());System.out.println("targetMethod.getDeclaringClass().getName() = " + targetMethod.getDeclaringClass().getName());System.out.println("===========targetMethod before==========");// 可能出現的情況// 情況一:methodProxy.invoke(proxy, args) 陷入invoke -> intercept -> invoke ->...的無限遞歸中//// 情況二:targetMethod.invoke(proxy, args) 陷入 intercept->show->intercept的死循環中。// 這是因為targetMethod是一個Method方法對象,只有invoke方法,此時沒有涉及到MethodProxy的invoke和invokeSuper方法// 而proxy是targetClass的一個子類對象,因此targetMethod.invoke(proxy, args)相當于在調用proxy對象中的同名targetMethod方法,即增強方法。所以陷入死循環// Spring使用this無法增強的原因,使用下面的情況三方案,其中target對象是外部傳入的,和MethodInvocationHandler一樣// 情況三:methodProxy.invoke(target, args)//Object result = methodProxy.invokeSuper(proxy, args);System.out.println("===========targetMethod after===========");return result;}
}/*** 目標類(被代理類)不存在接口*/
class ServiceImpl {public void show(String msg) {System.out.println(msg);}public String getMsg(int x, int y) {this.show("Hello World");return String.valueOf(x + y);}
}
代理類和目標類的關系結構圖
代理類字節碼
對反編譯后的源代碼文件進行變量名的調整,刪減一些非核心的細節內容。
從重寫的增強方法中可以看出來,傳遞給 intercept()
方法的參數的含義分別是:
Object
:(ServiceImplCglibProxy)代理類對象(this)Method
:代理類中對于目標類方法的對象引用,即上圖中的Method01
、Method02
、Method03
Object[]
:方法參數數組(args)MethodProxy
:根據代理類中的cglibMethod0x()
和method0x()
生成的 MethodProxy 對象
public class ServiceImplCglibProxyextends ServiceImplimplements Factory {// 目標類中的的方法對象(show)private static Method showMethod;// 代理類中show方法和cglibShow方法共同構建的MethodProxy對象private static MethodProxy showMethodProxy;// 目標類中的的方法對象(show)private static Method getMsgMethod;// 代理類中getMsg方法和cglibGetMsg方法共同構建的MethodProxy對象private static MethodProxy getMsgMethodProxy;// 用來包裝Object[0]private static final Object[] emptyArgs = new Object[0];// Object類中的方法private static Method finalizeMethod;private static MethodProxy finalizeMethodProxy;// 判斷是否綁定過ThreadLocal中的Callback,如果綁定過,那么之后就不需要綁定了private boolean bound;// 自定義增強邏輯,MethodInterceptor是Callback的一個子接口,在實例化的時候會通過調用靜態方法進行設置,省略private MethodInterceptor methodInterceptor;static {init();}@SneakyThrowsstatic void init() {// 獲取當前代理類的字節碼對象Class proxyClass = Class.forName("org.example.ServiceImplCglibProxy");Method[] methods;// 處理Object類中的所有方法Class targetClass = Class.forName("java.lang.Object");methods = ReflectUtils.findMethods(new String[]{"finalize", "()V","equals", "(Ljava/lang/Object;)Z","toString", "()Ljava/lang/String;","hashCode", "()I","clone", "()Ljava/lang/Object;"},targetClass.getDeclaredMethods());finalizeMethod = methods[0];finalizeMethodProxy = MethodProxy.create(targetClass, proxyClass, "()V", "finalize", "cglibFinalize");// 省略 Object 類中的其他方法的處理...// 處理ServiceImpl(父類)中的所有方法targetClass = Class.forName("org.example.ServiceImpl");methods = ReflectUtils.findMethods(new String[]{"show", "(Ljava/lang/String;)V","getMsg", "(II)Ljava/lang/String;"},targetClass.getDeclaredMethods());// 處理ServiceImpl中的show方法showMethod = methods[0];showMethodProxy = MethodProxy.create(targetClass, proxyClass, "(Ljava/lang/String;)V", "show", "cglibShow");// 處理ServiceImpl中的getMsg方法getMsgMethod = methods[1];getMsgMethodProxy = MethodProxy.create(targetClass, proxyClass, "(II)Ljava/lang/String;", "getMsg", "cglibGetMsg");}public static MethodProxy findMethodProxy(Signature signature) {String methodSignature = signature.toString();// 先比較hashCode值,再比較字符串switch (methodSignature.hashCode()) {case -1574182249:if (methodSignature.equals("finalize()V")) {return finalizeMethodProxy;}break;case 550733602:if (methodSignature.equals("show(Ljava/lang/String;)V")) {return showMethodProxy;}break;case 351083702:if (methodSignature.equals("getMsg(II)Ljava/lang/String;")) {return getMsgMethodProxy;}break;}return null;}// 為便于區分,稱為cglib方法void cglibShow(String msg) {super.show(msg);}// 為便于區分,稱為重寫方法@SneakyThrows@Overridepublic void show(String msg) {if(methodInterceptor == null){// 當使用methodProxy.invoke(target, args),由于target沒有methodInterceptor,所有會進入到這個邏輯分支中super.show(msg);return;}// 將參數封裝成Object數組,調用intercept方法// 在正常傳遞MethodInterceptor的情況下,會調用該方法methodInterceptor.intercept(this, showMethod, new Object[]{msg}, showMethodProxy);}// 為便于區分,稱為cglib方法String cglibGetMsg(int x, int y) {return super.getMsg(x, y);}// 為便于區分,稱為重寫方法@SneakyThrows@Overridepublic String getMsg(int x, int y) {if(methodInterceptor == null){return super.getMsg(x, y);}return (String) methodInterceptor.intercept(this, getMsgMethod, new Object[]{x, y}, getMsgMethodProxy);}}
增強方法的調用流程圖
在進一步討論增強方法的調用流程之前,先重新查看 MethodInterceptor 的 intercept()
方法。我們可以得到四個參數,那么怎么選擇來達成我們調用原始方法的目的。
結論:
- 在不考慮引入額外的目標類對象 target,僅使用 intercept 方法中提供的四個參數的情況下,只有
invokeSuper()
方法能夠正確執行,同時 this 指針指向的是 ServiceImplCglibProxy(代理類)對象,因為不會出現像 Spring 中使用 @Transactional 標注的方法中使用 this 會造成事務失效的問題。 - 在使用額外引入的目標類對象 target 的情況下(和 JDK 動態代理的 InvocationHandler 類似,但 JDK Proxy 強制要求一個目標類對象,而 Cglib Proxy 并不要求目標類對象),除了
invokeSuper()
方法外,另外兩個方法都可以正確執行。 - **使用 MethodProxy 對象和 Method 對象的區別在于,MethodProxy 借助額外生成的字節碼 FastClass 來實現對方法的直接調用,而 Method 則是通過反射來調用方法。**因此 MethodProxy 方式調用可以解決 Spring 的 this 造成的事務失效問題,但 Spring 是故意設計成和 JDK Proxy 的效果一樣,為此還舍棄 invokeSuper 的調用方式,而是引入 target 來使用 invoke 調用。這樣設計的官方解釋沒有仔細了解,個人分析,為了統一結果,因為 JDK Proxy 是通過反射來實現的,因此 this 只能代表目標類對象;如果僅僅因為使用的動態代理不同,就造成有的時候 this 失效,有的時候 this 有效,那么就憑空多添加了混亂。因此還不如都設計成 this 失效。
class MyMethodInterceptor implements MethodInterceptor {// 不是必要的,Spring注入target造成this無法調用自身對象,直接使用proxy對象就不會出現這種情況private Object target;@Overridepublic Object intercept(Object proxy, Method targetMethod, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("===========targetMethod before==========");// 由于FastClass的機制,這里不能調用methodProxy.invoke(proxy, args),否則// 正常可能出現的情況// 情況一:methodProxy.invoke(proxy, args) 陷入invoke -> intercept -> invoke ->...的無限遞歸中//// 情況二:targetMethod.invoke(proxy, args) 陷入 intercept->show->intercept的死循環中。// 這是因為targetMethod是一個Method方法對象,只有invoke方法,此時沒有涉及到MethodProxy的invoke和invokeSuper方法// 而proxy是targetClass的一個子類對象,因此targetMethod.invoke(proxy, args)相當于在調用proxy對象中的同名targetMethod方法,即增強方法。所以陷入死循環// Spring使用this無法增強的原因,使用下面的情況三方案,其中target對象是外部傳入的,和MethodInvocationHandler一樣// 情況三:methodProxy.invoke(target, args)//Object result = methodProxy.invokeSuper(proxy, args);System.out.println("===========targetMethod after===========");return result;}
}
MethodProxy
主要關注 invoke()
方法和 invokeSuper()
方法,因為這兩個方法會在 MethodInterceptor 對象的 intercept()
方法中被我們調用來達到調用目標類中的原始方法的目的。
public class MethodProxy {private Signature overrideMethodSignature;private Signature cglibMethodSignature;// create方法中生成,init方法中清空private CreateInfo createInfo;private final Object initLock = new Object();private volatile FastClassInfo fastClassInfo;public static MethodProxy create(Class targetClass, Class proxyClass, String desc, String overrideMethodName, String cglibMethodName) {MethodProxy proxy = new MethodProxy();proxy.overrideMethodSignature = new Signature(overrideMethodName, desc);proxy.cglibMethodSignature = new Signature(cglibMethodName, desc);proxy.createInfo = new CreateInfo(targetClass, proxyClass);return proxy;}public Object invoke(Object object, Object[] args) throws Throwable {this.init();FastClassInfo fci = this.fastClassInfo;// 默認情況下 object 是 proxyClass 類型,那么 overrideMethod -> intercept -> invoke -> overrideMethod 形成死循環。// 如果按照Spring的方式手動傳遞 targetClass 類型的對象,object是targetClass類型(目標類)return fci.targetFastClass.invoke(fci.overrideMethodIndex, object, args);}public Object invokeSuper(Object proxy, Object[] args) throws Throwable {this.init();FastClassInfo fci = this.fastClassInfo;// 重寫父類方法時,留了一份拷貝,稱之為cglibMethod,因此可以在當前類直接調用父類中的同名方法return fci.proxyFastClass.invoke(fci.cglibMethodIndex, proxy, args);}private void init() {if (this.fastClassInfo == null) {synchronized (this.initLock) {if (this.fastClassInfo == null) {CreateInfo ci = this.createInfo;FastClassInfo fci = new FastClassInfo();fci.targetFastClass = helper(ci, ci.targetClass);fci.proxyFastClass = helper(ci, ci.proxyClass);// overrideMethodSignature在targetClass和proxyClass中的簽名都相同fci.overrideMethodIndex = fci.targetFastClass.getIndex(this.overrideMethodSignature);fci.cglibMethodIndex = fci.proxyFastClass.getIndex(this.cglibMethodSignature);this.fastClassInfo = fci;this.createInfo = null;}}}}private static FastClass helper(CreateInfo ci, Class classType) {FastClass.Generator generator = new FastClass.Generator();generator.setType(classType);generator.setClassLoader(ci.proxyClass.getClassLoader());generator.setNamingPolicy(ci.namingPolicy);generator.setStrategy(ci.strategy);generator.setAttemptLoad(ci.attemptLoad);return generator.create();}private MethodProxy() {}public Signature getSignature() {return this.overrideMethodSignature;}public String getSuperName() {return this.cglibMethodSignature.getName();}public int getSuperIndex() {this.init();return this.fastClassInfo.cglibMethodIndex;}FastClass getFastClass() {this.init();return this.fastClassInfo.targetFastClass;}FastClass getSuperFastClass() {this.init();return this.fastClassInfo.proxyFastClass;}@SneakyThrowspublic static MethodProxy find(Class classType, Signature sig) {Method method = classType.getDeclaredMethod("findMethodProxy", MethodInterceptorGenerator.FIND_PROXY_TYPES);return (MethodProxy) method.invoke(null, sig);}private static class CreateInfo {Class targetClass;Class proxyClass;NamingPolicy namingPolicy;GeneratorStrategy strategy;boolean attemptLoad;public CreateInfo(Class targetClass, Class proxyClass) {this.targetClass = targetClass;this.proxyClass = proxyClass;AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();if (fromEnhancer != null) {this.namingPolicy = fromEnhancer.getNamingPolicy();this.strategy = fromEnhancer.getStrategy();this.attemptLoad = fromEnhancer.getAttemptLoad();}}}private static class FastClassInfo {FastClass targetFastClass;FastClass proxyFastClass;int overrideMethodIndex;int cglibMethodIndex;private FastClassInfo() {}}
}
FastClass
為了避免使用 Method 的 invoke 方法來進行反射調用而設計的。
TargetFastClass
public Object invoke(int methodIndex, Object obj, Object[] args) throws InvocationTargetException {// 這里無論傳入的是ServiceImpl還是ServiceImplCglibProxy,都可以進行強轉// 如果是ServiceImplCglibProxy,就調用同名方法ServiceImpl serviceImpl = (ServiceImpl)obj;// 沒有匹配就拋出異常try {switch (methodIndex) {case 0:return serviceImpl.getMsg(((Number)args[0]).intValue(), ((Number)args[1]).intValue());case 1:// 如果傳入的實際對象是ServiceImplCglibProxy類型的,那么就會調用增強方法,而增強方法又會進而到intercept中,從而造成死循環serviceImpl.show((String)args[0]);return null;}} catch (Throwable throwable) {throw new InvocationTargetException(throwable);}throw new IllegalArgumentException("Cannot find matching method/constructor");
}
ProxyFastClass
public Object invoke(int methodIndex, Object obj, Object[] args) throws InvocationTargetException {// 強轉成代理對象(ServiceImplCglibProxy)ServiceImplCglibProxy serviceImplProxy = (ServiceImplCglibProxy)obj;// 沒有匹配就拋出異常try {// ProxyFastClass的索引順序和TargetFastClass的索引順序沒有任何關系switch (methodIndex) {case 0:return serviceImpl.getMsg(((Number)args[0]).intValue(), ((Number)args[1]).intValue());case 1:return serviceImpl.cglibGetMsg(((Number)args[0]).intValue(), ((Number)args[1]).intValue());case 2:serviceImpl.cglibShow((String)args[0]);return null;case 3:serviceImpl.show((String)args[0]);return null;}} catch (Throwable throwable) {throw new InvocationTargetException(throwable);}throw new IllegalArgumentException("Cannot find matching method/constructor");
}