Spring @Async/@Transactional 失效的原因及解決方案


 

這周開發自測剛好遇到了使用@Transactional和@Async的不生效的問題,參考網上資料后,發現這篇文章圖文並茂,講的非常清晰易懂,簡單做了些補充搬運至此。

實現AOP的方法有動態代理、編譯期,類加載期織入等等,Spring實現AOP的方法則就是利用了動態代理機制,正因如此,才會導致某些情況下@Async和@Transactional不生效。

@EnableAsync //添加此注解開啟異步調用
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

當某些任務執行時間較長,且客戶端不需要及時獲取結果(如調用第三方API),只要在需要異步調用的任務上添加 @Async即可,如:

@Async //可以@Async("")指定自定義線程池(beanName)
void asyncTask(String keyword) {
    try {
        // 模擬處理過程  
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        //logger
        //error tracking
    }
    System.out.println(keyword);
}

這樣asyncTask就會異步執行。 然而,如果在同一個Class內(比如業務邏輯同在一個XXXServiceImpl),出現下面這樣的情況,先調用一個非異步任務

private void noAsyncTask(String keyword){
    asyncTask(keyword); //該方法內再調用異步方法
}

@Async
void asyncTask(String keyword) {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        //logger
        //error tracking
    }
    System.out.println(keyword);
}

此時,@Async是沒有生效的。 原因就是@Async和@Transaction利用了動態代理機制。

當Spring發現@Transactional或者@Async時,會自動生成一個ProxyObject,如:

 

此時調用Class.transactionTask會調用ProxyClass.產生事務操作。
然而當Class里的一個非事務方法調用了事務方法,ProxyClass是這樣的:

 

 

到這里應該可以看明白了,如果調用了noTransactionTask方法,最終會調用到Class.transactionTask,而這個方法是不帶有任何Transactional的信息的,也就是@Transactional根本沒有生效哦。
簡單來說就是: 同一個類內這樣調用的話,只有第一次調用了動態代理生成的ProxyClass,之后一直用的是不帶任何切面信息的方法本身。

知道了原因,處理方法也特別簡單,就是讓noTransactionTask里依舊調用ProxyClass的transactionTask方法:只需要顯示利用Spring暴露的AopContext即可。代碼如下:

private void noAsyncTask(String keyword){
    // 注意這里 調用了代理類的方法, 或者直接在spring上下文獲取bean(如果有aop,單例池中存放的是織入aop的代理類)
    ((YourClass) AopContext.currentProxy()).asyncTask(keyword);
//
SpringContextUtil.getBean(YourClass.class).asyncTask(keyword);
} @Async void asyncTask(String keyword) { try { Thread.sleep(5000); } catch (InterruptedException e) { //logger //error tracking  } System.out.println(keyword); }
 
           
如果使用AopContext記得要在Class上加上@EnableAspectJAutoProxy(exposeProxy = true)來暴露AOP的Proxy對象才行,否則會報錯。

或者就可以把這樣的方法放到另外一個類里,不要產生類里一個非異步/非事務方法,調用了異步/事務方法,不過大家協同開發同一個文件的話,誰能保證沒有人這樣調用呢?總而言之無論什么方案,都是使得調用ProxyObject的方法。

 

如有錯誤,請批評指正。

 

 

 
 
=========割了更健康===============
參考原文
作者:陶源0111
鏈接:https://www.jianshu.com/p/9a0de6577ed7


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM