轉(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)原理
- 編譯時(shí)織入:在代碼編譯時(shí),把切面代碼融合進(jìn)來,生成完整功能的Java字節(jié)碼,這就需要特殊的Java編譯器了,AspectJ屬于這一類
- 類加載時(shí)織入:在Java字節(jié)碼加載時(shí),把切面的字節(jié)碼融合進(jìn)來,這就需要特殊的類加載器,AspectJ和AspectWerkz實(shí)現(xiàn)了類加載時(shí)織入
- 運(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)