一、前言
很多小伙伴在初次使用springboot框架@Async注解時,可能會發現明明在方法上添加了@Async注解,並且也在啟動類上添加了@EnableAsync注解,但是方法依舊沒有異步的去執行。
二、思考
很大可能性是因為是在同一個類里面,一個方法去調用另外一個有@Async注解的方法,這種情況下異步方法是不會有效果的(@Transational也是同理)。
除此之外,在使用springboot框架執行異步方法時,有以下幾點需要注意
- 必須在啟動類中增加@EnableAsync注解;
- 異步類沒有被springboot管理,在有異步方法的類上添加@Component注解(或其他注解)且保證可以掃描到異步類;
- 測試異步方法不能與異步方法在同一個類中;
- 測試類中需要使用spring容器初始化的異步類,不能自己手動new對象;
三、原因分析
上面的1、2、4點大家都會注意到,但是經常會忽視第3點,那么為什么在同一個類里面,用一個方法去調用另外一個有@Async注解的方法,異步方法不會生效呢?
原來,spring 在掃描bean的時候會掃描該類的方法上是否包含
@Async 注解,如果包含,spring會為這個bean動態地生成一個子類(即代理類
proxy),代理類是繼承原來那個bean的,並且重寫了父類中被
@Async 注解的方法(如果該注解是加在了類上,則會重寫該類的所有方法),並利用AOP切面為這些方法加上異步邏輯。
此時,當這個有注解的方法被調用的時候,實際上調用的是代理類中重寫過的方法。然而,如果這個有注解的方法是被同一個類中的其他方法調用的,那么就不會調用該代理類了,而是直接通過當前對象去調,所以也就不生效了,所以我們看到的現象就是該異步方法沒有生效。
具體實現原理可以看這篇文章
深入理解Spring系列之十五:@Async實現原理
為什么一個方法a()調用同一個類中另外一個方法b()的時候,b()不是通過代理類來調用的呢?可以看下面的例子(為了簡化,用偽代碼表示):
@Service class A{ @Async method b(){...} method a(){ //標記1 b(); } } //Spring掃描注解后,會創建另外一個代理類,並對添加注解的方法根據切入點創建代理 //調用代理,執行切入點處理器invoke方法,實現異步執行 class proxy$A{ A objectA = new A(); method b(){ //標記2 //MethodInterceptor.invoke objectA.b(); } method a(){ //標記3 objectA.a(); //由於a()沒有注解,所以不會創建代理,而是直接調用A的實例的a()方法 } }
當我們調用A的bean的a()方法的時候,也是被proxy$A攔截,執行proxy$A.a()(標記3),然而,由以上代碼可知,這時候它調用的是objectA.a(),也就是由原來的bean來調用a()方法了,所以代碼跑到了“標記1”。由此可見,“標記2”並沒有被執行到,所以startTransaction()方法也沒有運行。
四、代碼實踐
根據上面的注意點,我們可以使用以下四種情況進行測試
- 1st:調用當前類中帶有@Async注解的方法...
- 2nd:調用其他類Test類中帶有@Async注解的方法...
- 3rd:調用其他類Test1類不帶@Async注解的方法...
- 4th:調用其他類Test2類(沒有被springboot初始化的類)中帶有Async注解的方法...
代碼如下:
@Slf4j @Component public class TestAsync { @Autowired private Test test; @Autowired private Test1 test1; public void testCommon(){ log.info("主線程名:{}"+Thread.currentThread().getName()); log.info("1st:調用當前類中帶有@Async注解的方法..."); this.testAsync(); log.info("當前類名:{}",this.getClass().getName()); log.info("==========================================="); log.info("2nd:調用其他類Test類中帶有@Async注解的方法..."); test.testAsync(); Class c= test.getClass(); log.info("當前類名:{}",c.getName()); log.info("Test類的父類:{}",c.getSuperclass().getName()); log.info("==========================================="); log.info("3rd:調用其他類Test1類不帶@Async注解的方法..."); test1.test(); log.info("當前類名:{}",test1.getClass().getName()); log.info("==========================================="); log.info("4th:調用其他類Test2類(沒有被springboot初始化的類)中帶有Async注解的方法..."); Test2 test2 = new Test2(); test2.testAsync(); log.info("當前類名:{}",test2.getClass().getName()); } @Async public void testAsync(){ log.info("當前類調用線程名:"+Thread.currentThread().getName()); } }
@Slf4j @Component public class Test { @Async public void testAsync(){ log.info("Test類線程名:"+Thread.currentThread().getName()); } public void method(){ log.info("this is Test.method"); } }
@Slf4j @Component public class Test1 { public void test() { log.info("Test1類線程名:"+Thread.currentThread().getName()); } }
@Slf4j public class Test2 { public void testAsync() { log.info("Test2類線程名:"+Thread.currentThread().getName()); } }
測試結果如下:
通過類名可以看出當前只有情況2有效的,它是使用的代理類,並且該代理類是目標類的子類。
五、總結
這篇文章主要分析了在spingboot中可能導致@Async注解失敗的幾種情況以及如何解決。
文章參考:
https://blog.csdn.net/clementad/article/details/47339519?utm_source=copy