事情要從一次奇怪的 bug 說起甜攀, 過程大約是這樣的捐寥, 在一次使用 @Transactional 注解的過程中胸墙, 為了分離真正需要事物的代碼和其他業(yè)務邏輯寡润, 我對代碼進行了抽取捆憎, 抽取后的代碼類似于下面這樣:
但是在進行測試時發(fā)現雖然加上了事物注解, 但是事物卻完全沒有生效梭纹。 當時可以說是百思不得其解躲惰, 只能猜測是因為 aop 是通過 jdk 動態(tài)代理方式實現的, 所以 commitSomething 方法并未聲明在接口上变抽, 自然也就不能被代理础拨, 也就不能實現事物。 為驗證這個猜測绍载, 修改 proxyTargetClass=true 切換為 cglib 形式代理诡宗, 我們知道, cglib 是通過子類化來實現的代理击儡, 具體生成的代碼類似于下面這樣:
// 父類代碼
public class AService {
pubiic void consume() {
// do something
}
}
// 生成的代理子類
public class AService$EnhanceByCGLIB$$xxxxx extends AService {
private MethodInterceptor CGLIB$CALLBACK_0;
final void CGLIB$consume$0(){
super.consume();
}
public final void consume() {
MethodIterceptor methodIterceptor = CGLIB$CALLBACK_0;
if (methodIterceptor == null) {
CGLIB$BIND_CALLBACKS(this);
methodIterceptor = CGLIB$CALLBACK_0;
}
if (this.CGLIB$CALLBACK_0 != null) {
methodIterceptor.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy);
}
else {
CGLIB$consume$0();
}
}
}
但是修改為 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 負責創(chuàng)建 豺鼻。AopProxy有兩個實現類 JdkDynamicAopProxy 和 CglibAopProxy 繼承關系如下圖:
選擇代理類
Spring 默認使用 jdk dynamic proxy 方式實現 AOP, 當被代理類無接口時 Spring 使用 cglib 方式實現代理。創(chuàng)建 AopProxy 邏輯主要在類DefaultAopProxyFactory中代碼如下:
可以看到判斷使用哪一種代理實現邏輯是根據 config.isProxyTargetClass 來判斷的区拳, 而這個 config 是一個AdvisedSupport 的實例拘领。 AdvisedSupport 是管理Spring AOP 配置的類, 類層次結構如下:
而 Spring 正是通過 ProxyFactory getProxy 來構造的代理樱调。
ProxyFactory 的 isProxyTargetClass 也會在真正創(chuàng)建代理對象時提前計算好约素。
創(chuàng)建并調用ProxyFacotry在類 AnnotationAwareAspectJAutoProxyCreator 中代碼如下:
可以看到 AnnotationAwareAspectJAutoProxyCreator 的 createProxy 主要做了兩件事,
- 1.創(chuàng)建 ProxyFactory 并初始化 ProxyFactory 的一些構造代理相關的屬性
- 2.創(chuàng)建代理 bean
代理類的調用與執(zhí)行
因為 JdkDynamicAopProxy就是基于 Java的動態(tài)代理機制相信大家都比較熟悉笆凌, 這里就略過不講圣猎。后文主要講下 CglibAopProxy 調用與執(zhí)行。
在前文中可以看到 Spring 是通過 AnnotationAwareAspectJAutoProxyCreator的createProxy方法來構造具有AOP能力的 bean乞而, 這個過程就是設置好代理的攔截器以及一些其他與構造代理相關屬性后將創(chuàng)建過程委托給 JdkDynamicAopProxy 或 CglibAopProxy 中具體一個送悔, 再由 JdkDynamicAopProxy 或 CglibAopProxy 調用 getProxy 創(chuàng)建代理 bean。 所以實際我們通過 @Autowired 注解或者從 Spring 上下文中拿到的 bean 就已經是生成好的代理 bean了爪模。 可以 debug 看下欠啤, 獲取的 bean 的 class 一般是 $Proxy 或 $EnhanceByCGLIB 這樣的后綴。
繼續(xù)看下代理攔截邏輯也就是 AOP 的切面邏輯是如何實現的屋灌, 在 CglibAopProxy 構造代理對象時代理攔截邏輯是通過如下代碼實現注冊:
而 Spring 主要是通過 DynamicAdvisedInterceptor 類實現攔洁段,截繼承關系如圖:
DynamicAdvisedInterceptor 的 intercept 實現了具體的攔截邏輯:
可以看到具體實現邏輯首先是獲取攔截器鏈, 再通過 CglibMethodInvocation 來執(zhí)行攔截器鏈, 具體再看 CglibMethodInvocation 的 invokeJoinpoint 方法共郭, 具體邏輯如下:
Spring 通過攔截器鏈對被調用方法進行動態(tài)匹配如果匹配上則進行連接器調用否則繼續(xù)往后面鏈路執(zhí)行當所有攔截器都被執(zhí)行后祠丝, 調用
methodProxy.invoke(target, arguments)
執(zhí)行被代理方法。 至此 Spring Proxy base 代理整個流程基本梳理完畢除嘹。
總結
理解完spring 整個 aop 實現邏輯也就可以理解前文 bug, 其實前文中不能應用上事物問題写半,主要原因是內部方法調用引起的。 因為在方法內部尉咕, 作用域就不是 proxy 對象叠蝇, 而是 target source (被代理源對象), 所以理解了原因解決這個問題自然也就簡單了:
主要可以通過
- 1.AopContext.currentProxy() 獲取代理對象
- 2.從Spring 上下文中獲取代理對象
- 3.使用 AspectJ 方式實現 Aop