zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    一、zuul如何路由到上游服务器
    一、hystrix如何集成在openfeign中使用
    一、ribbon如何集成在openfeign中使用
    二、openfeign生成并调用客户端动态代理对象
    一、openfeign的自动配置
    BootStrap【一、概述】
    JavaSpring【七、AspectJ】
    JavaSpring【六、AOP的API】
    JavaSpring【五、AOP基础】
    目录整理
  • 原文地址:https://www.cnblogs.com/sueyyyy/p/14212953.html
Copyright © 2011-2022 走看看