從Spring AOP的原理理解@Transactional失效問題

轉(zhuǎn)自:https://blog.csdn.net/canot/article/details/80855439,如有侵權(quán),請(qǐng)聯(lián)系我刪除,謝謝

在正確配置了Spring事務(wù)管理后,或許在某些場景下,你可以寫出如下代碼:

class T {

    public int createFirst(){
       //dosometing....
       try {
           this.createSecond();
       }catch (Exception e){
          throw e;
       }
       //dosometing....
       return 0;
    }


    @Transactional
    public int createSecond(){
       //dosometing with db....

    }
}

然后在外部通過Spring容器獲取T的實(shí)例t,當(dāng)你調(diào)用t.createFirst()方法的時(shí)候,你會(huì)發(fā)現(xiàn)添加了@Transactional注解的createSecond()方法并沒有運(yùn)行在事務(wù)中。
為了了解這個(gè)問題的原理,首先先需要了解Spring 是如何處理注解的(@Transaction 或 @Async)。

AOP
面向切面編程,通俗一點(diǎn)將即是在要執(zhí)行的方法前或后執(zhí)行一段代碼。類使用Spring MVC中的過濾器,在請(qǐng)求前后執(zhí)行額外的邏輯,并且該邏輯對(duì)實(shí)際的方法是透明的。

AOP的實(shí)現(xiàn)原理

  1. 編譯時(shí)織入:在代碼編譯時(shí),把切面代碼融合進(jìn)來,生成完整功能的Java字節(jié)碼,這就需要特殊的Java編譯器了,AspectJ屬于這一類
  2. 類加載時(shí)織入:在Java字節(jié)碼加載時(shí),把切面的字節(jié)碼融合進(jìn)來,這就需要特殊的類加載器,AspectJ和AspectWerkz實(shí)現(xiàn)了類加載時(shí)織入
  3. 運(yùn)行時(shí)織入:在運(yùn)行時(shí),通過動(dòng)態(tài)代理的方式,調(diào)用切面代碼增強(qiáng)業(yè)務(wù)功能,Spring采用的正是這種方式。動(dòng)態(tài)代理會(huì)有性能上的開銷,但是好處就是不需要神馬特殊的編譯器和類加載器啦,按照寫普通Java程序的方式來就行了!

Spring AOP 屬于運(yùn)行時(shí)織入,即使用代理模式?;蛟S你看到Spring AOP依賴了AspectJ包會(huì)以為Spring AOP是AspectJ來實(shí)現(xiàn)的。但其他并不是,Spring AOP只是使用了和AspectJ一樣的注解但并沒有使用 AspectJ 的編譯器或者織入器(Weaver),底層AOP實(shí)現(xiàn)與AspectJ是不同的。Spring AOP 無需使用任何特殊命令對(duì) Java 源代碼進(jìn)行編譯,它采用運(yùn)行時(shí)動(dòng)態(tài)地、在內(nèi)存中臨時(shí)生成“代理類”的方式來生成 AOP 代理。

Spring AOP的代理實(shí)現(xiàn)

1.JDK動(dòng)態(tài)代理:JDK動(dòng)態(tài)代理技術(shù)。通過需要代理的目標(biāo)類的getClass().getInterfaces()方法獲取到接口信息(這里實(shí)際上是使用了Java反射技術(shù)。getClass()和getInterfaces()函數(shù)都在Class類中,Class對(duì)象描述的是一個(gè)正在運(yùn)行期間的Java對(duì)象的類和接口信息),通過讀取這些代理接口信息生成一個(gè)實(shí)現(xiàn)了代理接口的動(dòng)態(tài)代理Class(動(dòng)態(tài)生成代理類的字節(jié)碼),然后通過反射機(jī)制獲得動(dòng)態(tài)代理類的構(gòu)造函數(shù),并利用該構(gòu)造函數(shù)生成該Class的實(shí)例對(duì)象(InvokeHandler作為構(gòu)造函數(shù)的入?yún)鬟f進(jìn)去),在調(diào)用具體方法前調(diào)用InvokeHandler來處理。
2.CGLib動(dòng)態(tài)代理:字節(jié)碼技術(shù)。利用asm開源包,把代理對(duì)象類的class文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理。采用非常底層的字節(jié)碼技術(shù),為一個(gè)類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,并順勢織入橫切邏輯。

在我們實(shí)際使用Spring AOP時(shí),它包括基于XML配置的AOP和基于@AspcetJ注解的AOP,這兩種方法雖然在配置切面時(shí)的表現(xiàn)方式不同,但底層都是使用動(dòng)態(tài)代理技術(shù)(JDK代理或CGLib代理)。

現(xiàn)在回頭來看本文開頭的代碼
createFirst()方法里面直接調(diào)用createSecond(……)方法,這里還隱含一個(gè)關(guān)鍵字,那就是this,實(shí)際上這里調(diào)用是這樣的:this.createSecond(),this是當(dāng)前對(duì)象。當(dāng)前對(duì)象是T,問題就出在這里,因?yàn)橐胗檬聞?wù)執(zhí)行createSecond(……),必須用代理對(duì)象執(zhí)行,因?yàn)榇韺?duì)象會(huì)攔截到@Transactional來執(zhí)行相關(guān)的增強(qiáng),但是此時(shí)卻直接用T對(duì)象調(diào)用,繞過了代理對(duì)象增強(qiáng)的部分,也就是說代理增強(qiáng)部分失效,@Transactional注解失效。

如果在外部通過Spring容器獲取T實(shí)例,然后直接調(diào)用createSecond方法,此時(shí)createSecond()是被代理的,在代理對(duì)象中判斷到該方法有@Transactional,則會(huì)執(zhí)行該注解需要的前置增強(qiáng)后(開啟事務(wù)),然后通過invoke,用實(shí)際T對(duì)象來調(diào)用addOrder()方法執(zhí)行業(yè)務(wù)邏輯,然后執(zhí)行后置增強(qiáng)(回滾or提交)。

特殊場景的解決辦法
沒有用代理對(duì)象執(zhí)行createSecond(……),被T對(duì)象搶占了先機(jī)。那么解決就是要讓代理對(duì)象來執(zhí)行createSecond(……)。

T t = (T) AopContext.currentProxy(); 
//獲取代理對(duì)象
t.createSecond(); 
//通過代理對(duì)象調(diào)用createSecond

//需要在@EnableAspectJAutoProxy添加屬性值。
//@EnableAspectJAutoProxy(exposeProxy = true)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容