事情要從一次奇怪的 bug 說(shuō)起挫望, 過(guò)程大約是這樣的脊岳, 在一次使用 @Transactional 注解的過(guò)程中御雕, 為了分離真正需要事物的代碼和其他業(yè)務(wù)邏輯膏燃, 我對(duì)代碼進(jìn)行了抽取灌具, 抽取后的代碼類似于下面這樣:
但是在進(jìn)行測(cè)試時(shí)發(fā)現(xiàn)雖然加上了事物注解青团, 但是事物卻完全沒(méi)有生效。 當(dāng)時(shí)可以說(shuō)是百思不得其解咖楣, 只能猜測(cè)是因?yàn)?aop 是通過(guò) jdk 動(dòng)態(tài)代理方式實(shí)現(xiàn)的督笆, 因?yàn)閏ommitSomething 方法并未聲明在接口上, 自然也就不能被代理诱贿, 也就不能實(shí)現(xiàn)事物娃肿。 為驗(yàn)證這個(gè)猜測(cè), 修改 proxyTargetClass=true 切換為 cglib 形式代理珠十, 我們知道料扰, cglib 是通過(guò)子類化來(lái)實(shí)現(xiàn)的代理, 具體生成的代碼類似于下面這樣:
但是修改為 cglib代理方式后事物依然沒(méi)有生效焙蹭。為了弄清楚這個(gè)問(wèn)題晒杈, 我做了一些研究,并在這個(gè)過(guò)程中對(duì) Spring 實(shí)現(xiàn) Aop 的方式做了如下總結(jié):
我們知道 AOP 是Aspect Oriented Programming 面向切面編程的簡(jiǎn)稱孔厉,常被我們用作在事物拯钻, 日志,安全撰豺,緩存等方面粪般。
而 Spring 也實(shí)現(xiàn)了 AOP 機(jī)制,主要有兩種方式 :
1.Proxy base
2.AspectJ
第一種是基于代理方式實(shí)現(xiàn), 比如 JDK Dynamic Proxy 和 Cglib proxy
第二種是基于 AspectJ 框架織入源代碼方式實(shí)現(xiàn)郑趁。
下面我主要講下 Spring Proxy base AOP 方式的實(shí)現(xiàn)刊驴。
代理類構(gòu)成
Spring 默認(rèn)有兩種形式代理 jdk dynamic proxy 和 cglib proxy 在Spring 中這兩種形式代理由 AopProxy 負(fù)責(zé)創(chuàng)建 。AopProxy有兩個(gè)實(shí)現(xiàn)類 JdkDynamicAopProxy 和 CglibAopProxy 繼承關(guān)系如下圖:
選擇代理類
Spring 默認(rèn)使用 jdk dynamic proxy 方式實(shí)現(xiàn) AOP, 當(dāng)被代理類無(wú)接口時(shí) Spring 使用 cglib 方式實(shí)現(xiàn)代理寡润。創(chuàng)建 AopProxy 邏輯主要在類DefaultAopProxyFactory中代碼如下:
可以看到判斷使用哪一種代理實(shí)現(xiàn)邏輯是根據(jù) config.isProxyTargetClass 來(lái)判斷的, 而這個(gè) config 是一個(gè)AdvisedSupport 的實(shí)例舅柜。 AdvisedSupport 是管理Spring AOP 配置的類梭纹, 類層次結(jié)構(gòu)如下:
而 Spring 正是通過(guò) ProxyFactory getProxy 來(lái)構(gòu)造的代理。
ProxyFactory 的 isProxyTargetClass 也會(huì)在真正創(chuàng)建代理對(duì)象時(shí)提前計(jì)算好致份。
創(chuàng)建并調(diào)用ProxyFacotry在類 AnnotationAwareAspectJAutoProxyCreator 中代碼如下:
可以看到 AnnotationAwareAspectJAutoProxyCreator 的 createProxy 主要做了兩件事变抽,
1.創(chuàng)建并初始化 ProxyFactory
2.創(chuàng)建代理 bean
代理類的調(diào)用與執(zhí)行
由于 JdkDynamicAopProxy是基于 Java的動(dòng)態(tài)代理,相信大家都比較熟悉, 這里就略過(guò)不講绍载。后文主要講下 CglibAopProxy 調(diào)用與執(zhí)行诡宗。
在前文中可以看到 Spring 是通過(guò) AnnotationAwareAspectJAutoProxyCreator的createProxy方法來(lái)構(gòu)造具有AOP能力的 bean, 這個(gè)過(guò)程就是設(shè)置好代理的攔截器以及一些其他與構(gòu)造代理相關(guān)屬性后將創(chuàng)建過(guò)程委托給 JdkDynamicAopProxy 或 CglibAopProxy 中具體一個(gè)击儡, 再由 JdkDynamicAopProxy 或 CglibAopProxy 調(diào)用 getProxy 創(chuàng)建代理 bean塔沃。 所以實(shí)際我們通過(guò) @Autowired 注解或者從 Spring 上下文中拿到的 bean 就已經(jīng)是生成好的代理 bean了。 可以 debug 看下阳谍, 獲取的 bean 的 class 一般是 $Proxy 或 $EnhanceByCGLIB 這樣的后綴蛀柴。
繼續(xù)看下代理攔截邏輯也就是 AOP 的切面邏輯是如何實(shí)現(xiàn)的, 在 CglibAopProxy 構(gòu)造代理對(duì)象時(shí)代理攔截邏輯是通過(guò)如下代碼實(shí)現(xiàn)注冊(cè):
而 Spring 主要是通過(guò) DynamicAdvisedInterceptor 類實(shí)現(xiàn)攔矫夯,截繼承關(guān)系如圖:
DynamicAdvisedInterceptor 的 intercept方法實(shí)現(xiàn)了具體的攔截邏輯:
可以看到具體實(shí)現(xiàn)邏輯首先是獲取攔截器鏈鸽疾, 再通過(guò) CglibMethodInvocation 來(lái)執(zhí)行攔截器鏈, 具體再看 CglibMethodInvocation 的 invokeJoinpoint 方法, 具體邏輯如下:
Spring 通過(guò)攔截器鏈對(duì)被調(diào)用方法進(jìn)行動(dòng)態(tài)匹配如果匹配上則進(jìn)行連接器調(diào)用训貌,否則繼續(xù)往后面鏈路執(zhí)行制肮,當(dāng)所有攔截器都被執(zhí)行后調(diào)用
methodProxy.invoke(target, arguments)
執(zhí)行被代理方法。 至此 Spring Proxy base 代理整個(gè)流程基本梳理完畢递沪。
總結(jié)
理解完spring 整個(gè) aop 實(shí)現(xiàn)邏輯也就可以理解前文 bug, 其實(shí)前文中不能應(yīng)用上事物問(wèn)題豺鼻,主要原因是被代理方法的內(nèi)部方法調(diào)用引起的。 因?yàn)樵诜椒▋?nèi)部区拳, 作用域就不是 proxy 對(duì)象拘领, 而是 target source (被代理源對(duì)象), 所以理解了原因解決這個(gè)問(wèn)題自然也就簡(jiǎn)單了:
主要可以通過(guò)
1.AopContext.currentProxy() 獲取代理對(duì)象
2.從Spring上下文中獲取代理對(duì)象
3.使用 AspectJ 方式實(shí)現(xiàn) Aop
本文作者:馮劍濤(點(diǎn)融黑幫)樱调,主要從事Java 后端開(kāi)發(fā)工作约素,平時(shí)喜歡研究技術(shù),愛(ài)好是旅行和跑步笆凌。 目前主要負(fù)責(zé) crc & ams 系統(tǒng)圣猎。