事情要從一次奇怪的 bug 說起, 過程大約是這樣的, 在一次使用 @Transactional 註解的過程中, 為了分離真正需要事物的代碼和其他業務邏輯, 我對代碼進行了抽取, 抽取后的代碼類似於下面這樣:
但是在進行測試時發現雖然加上了事物註解, 但是事物卻完全沒有生效。 當時可以說是百思不得其解, 只能猜測是因為 aop 是通過 jdk 動態代理方式實現的, 因為commitSomething 方法並未聲明在介面上, 自然也就不能被代理, 也就不能實現事物。 為驗證這個猜測, 修改 proxyTargetClass=true 切換為 cglib 形式代理, 我們知道, cglib 是通過子類化來實現的代理, 具體生成的代碼類似於下面這樣:
但是修改為 cglib代理方式後事物依然沒有生效。為了弄清楚這個問題, 我做了一些研究,並在這個過程中對 Spring 實現 Aop 的方式做了如下總結:
我們知道 AOP 是Aspect Oriented Programming 面向切面編程的簡稱,常被我們用作在事物, 日誌,安全,緩存等方面。
而 Spring 也實現了 AOP 機制,主要有兩種方式 :
1.Proxy base
2.AspectJ
第一種是基於代理方式實現, 比如 JDK Dynamic Proxy 和 Cglib proxy
第二種是基於 AspectJ 框架織入源代碼方式實現。
下面我主要講下 Spring Proxy base AOP 方式的實現。
代理類構成
Spring 默認有兩種形式代理 jdk dynamic proxy 和 cglib proxy 在Spring 中這兩種形式代理由 AopProxy 負責創建 。AopProxy有兩個實現類 JdkDynamicAopProxy 和 CglibAopProxy 繼承關係如下圖:
選擇代理類
Spring 默認使用 jdk dynamic proxy 方式實現 AOP, 當被代理類無介面時 Spring 使用 cglib 方式實現代理。創建 AopProxy 邏輯主要在類DefaultAopProxyFactory中代碼如下:
可以看到判斷使用哪一種代理實現邏輯是根據 config.isProxyTargetClass 來判斷的, 而這個 config 是一個AdvisedSupport 的實例。 AdvisedSupport 是管理Spring AOP 配置的類, 類層次結構如下:
而 Spring 正是通過 ProxyFactory getProxy 來構造的代理。
ProxyFactory 的 isProxyTargetClass 也會在真正創建代理對象時提前計算好。
創建並調用ProxyFacotry在類 AnnotationAwareAspectJAutoProxyCreator 中代碼如下:
可以看到 AnnotationAwareAspectJAutoProxyCreator 的 createProxy 主要做了兩件事,
1.創建並初始化 ProxyFactory
2.創建代理 bean
代理類的調用與執行
由於 JdkDynamicAopProxy是基於 Java的動態代理,相信大家都比較熟悉, 這裡就略過不講。後文主要講下 CglibAopProxy 調用與執行。
在前文中可以看到 Spring 是通過 AnnotationAwareAspectJAutoProxyCreator的createProxy方法來構造具有AOP能力的 bean, 這個過程就是設置好代理的攔截器以及一些其他與構造代理相關屬性后將創建過程委託給 JdkDynamicAopProxy 或 CglibAopProxy 中具體一個, 再由 JdkDynamicAopProxy 或 CglibAopProxy 調用 getProxy 創建代理 bean。 所以實際我們通過 @Autowired 註解或者從 Spring 上下文中拿到的 bean 就已經是生成好的代理 bean了。 可以 debug 看下, 獲取的 bean 的 class 一般是 $Proxy 或 $EnhanceByCGLIB 這樣的後綴。
繼續看下代理攔截邏輯也就是 AOP 的切面邏輯是如何實現的, 在 CglibAopProxy 構造代理對象時代理攔截邏輯是通過如下代碼實現註冊:
而 Spring 主要是通過 DynamicAdvisedInterceptor 類實現攔,截繼承關係如圖:
DynamicAdvisedInterceptor 的 intercept方法實現了具體的攔截邏輯:
可以看到具體實現邏輯首先是獲取攔截器鏈, 再通過 CglibMethodInvocation 來執行攔截器鏈, 具體再看 CglibMethodInvocation 的 invokeJoinpoint 方法, 具體邏輯如下:
Spring 通過攔截器鏈對被調用方法進行動態匹配如果匹配上則進行連接器調用,否則繼續往後面鏈路執行,當所有攔截器都被執行后調用
methodProxy.invoke(target, arguments)
執行被代理方法。 至此 Spring Proxy base 代理整個流程基本梳理完畢。
總結
理解完spring 整個 aop 實現邏輯也就可以理解前文 bug, 其實前文中不能應用上事物問題,主要原因是被代理方法的內部方法調用引起的。 因為在方法內部, 作用域就不是 proxy 對象, 而是 target source (被代理源對象), 所以理解了原因解決這個問題自然也就簡單了:
主要可以通過
1.AopContext.currentProxy 獲取代理對象
2.從Spring 上下文中獲取代理對象
3.使用 AspectJ 方式實現 Aop
本文作者:馮劍濤(點融黑幫),主要從事Java 後端開發工作,平時喜歡研究技術,愛好是旅行和跑步。 目前主要負責 crc & ams 系統。