這周開發自測剛好遇到了使用@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