轉(zhuǎn)自:https://blog.csdn.net/canot/article/details/80855439磅氨,如有侵權(quán)醋奠,請聯(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的實例t陪竿,當(dāng)你調(diào)用t.createFirst()方法的時候禽翼,你會發(fā)現(xiàn)添加了@Transactional注解的createSecond()方法并沒有運行在事務(wù)中。
為了了解這個問題的原理族跛,首先先需要了解Spring 是如何處理注解的(@Transaction 或 @Async)瓮下。
AOP
面向切面編程妇菱,通俗一點將即是在要執(zhí)行的方法前或后執(zhí)行一段代碼。類使用Spring MVC中的過濾器,在請求前后執(zhí)行額外的邏輯漠秋,并且該邏輯對實際的方法是透明的祷肯。
AOP的實現(xiàn)原理
- 編譯時織入:在代碼編譯時萨西,把切面代碼融合進來芙代,生成完整功能的Java字節(jié)碼,這就需要特殊的Java編譯器了茉继,AspectJ屬于這一類
- 類加載時織入:在Java字節(jié)碼加載時咧叭,把切面的字節(jié)碼融合進來,這就需要特殊的類加載器烁竭,AspectJ和AspectWerkz實現(xiàn)了類加載時織入
- 運行時織入:在運行時菲茬,通過動態(tài)代理的方式,調(diào)用切面代碼增強業(yè)務(wù)功能,Spring采用的正是這種方式婉弹。動態(tài)代理會有性能上的開銷睬魂,但是好處就是不需要神馬特殊的編譯器和類加載器啦,按照寫普通Java程序的方式來就行了马胧!
Spring AOP 屬于運行時織入汉买,即使用代理模式∨寮梗或許你看到Spring AOP依賴了AspectJ包會以為Spring AOP是AspectJ來實現(xiàn)的。但其他并不是垫卤,Spring AOP只是使用了和AspectJ一樣的注解但并沒有使用 AspectJ 的編譯器或者織入器(Weaver)威彰,底層AOP實現(xiàn)與AspectJ是不同的。Spring AOP 無需使用任何特殊命令對 Java 源代碼進行編譯穴肘,它采用運行時動態(tài)地歇盼、在內(nèi)存中臨時生成“代理類”的方式來生成 AOP 代理。
Spring AOP的代理實現(xiàn)
1.JDK動態(tài)代理:JDK動態(tài)代理技術(shù)评抚。通過需要代理的目標(biāo)類的getClass().getInterfaces()方法獲取到接口信息(這里實際上是使用了Java反射技術(shù)豹缀。getClass()和getInterfaces()函數(shù)都在Class類中,Class對象描述的是一個正在運行期間的Java對象的類和接口信息)慨代,通過讀取這些代理接口信息生成一個實現(xiàn)了代理接口的動態(tài)代理Class(動態(tài)生成代理類的字節(jié)碼)邢笙,然后通過反射機制獲得動態(tài)代理類的構(gòu)造函數(shù),并利用該構(gòu)造函數(shù)生成該Class的實例對象(InvokeHandler作為構(gòu)造函數(shù)的入?yún)鬟f進去)侍匙,在調(diào)用具體方法前調(diào)用InvokeHandler來處理氮惯。
2.CGLib動態(tài)代理:字節(jié)碼技術(shù)。利用asm開源包想暗,把代理對象類的class文件加載進來妇汗,通過修改其字節(jié)碼生成子類來處理。采用非常底層的字節(jié)碼技術(shù)说莫,為一個類創(chuàng)建子類杨箭,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,并順勢織入橫切邏輯储狭。
在我們實際使用Spring AOP時互婿,它包括基于XML配置的AOP和基于@AspcetJ注解的AOP,這兩種方法雖然在配置切面時的表現(xiàn)方式不同晶密,但底層都是使用動態(tài)代理技術(shù)(JDK代理或CGLib代理)擒悬。
現(xiàn)在回頭來看本文開頭的代碼:
createFirst()方法里面直接調(diào)用createSecond(……)方法,這里還隱含一個關(guān)鍵字稻艰,那就是this懂牧,實際上這里調(diào)用是這樣的:this.createSecond(),this是當(dāng)前對象。當(dāng)前對象是T,問題就出在這里僧凤,因為要想用事務(wù)執(zhí)行createSecond(……)畜侦,必須用代理對象執(zhí)行,因為代理對象會攔截到@Transactional來執(zhí)行相關(guān)的增強躯保,但是此時卻直接用T對象調(diào)用旋膳,繞過了代理對象增強的部分,也就是說代理增強部分失效途事,@Transactional注解失效验懊。
如果在外部通過Spring容器獲取T實例,然后直接調(diào)用createSecond方法尸变,此時createSecond()是被代理的义图,在代理對象中判斷到該方法有@Transactional,則會執(zhí)行該注解需要的前置增強后(開啟事務(wù))召烂,然后通過invoke碱工,用實際T對象來調(diào)用addOrder()方法執(zhí)行業(yè)務(wù)邏輯,然后執(zhí)行后置增強(回滾or提交)奏夫。
特殊場景的解決辦法:
沒有用代理對象執(zhí)行createSecond(……)怕篷,被T對象搶占了先機。那么解決就是要讓代理對象來執(zhí)行createSecond(……)酗昼。
T t = (T) AopContext.currentProxy();
//獲取代理對象
t.createSecond();
//通過代理對象調(diào)用createSecond
//需要在@EnableAspectJAutoProxy添加屬性值廊谓。
//@EnableAspectJAutoProxy(exposeProxy = true)