springboot使用@Async注解時異步方法不生效原因分析及解決方案


一、前言

  很多小伙伴在初次使用springboot框架@Async注解時,可能會發現明明在方法上添加了@Async注解,並且也在啟動類上添加了@EnableAsync注解,但是方法依舊沒有異步的去執行。

二、思考

  很大可能性是因為是在同一個類里面,一個方法去調用另外一個有@Async注解的方法,這種情況下異步方法是不會有效果的(@Transational也是同理)。

  除此之外,在使用springboot框架執行異步方法時,有以下幾點需要注意

  1. 必須在啟動類中增加@EnableAsync注解;
  2. 異步類沒有被springboot管理,在有異步方法的類上添加@Component注解(或其他注解)且保證可以掃描到異步類;
  3. 測試異步方法不能與異步方法在同一個類中;
  4. 測試類中需要使用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

https://cloud.tencent.com/developer/article/1426027


免責聲明!

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



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