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切⾯为这些⽅法加上异步逻辑。
此时,当这个有注解的⽅法被调⽤的时候,实际上调⽤的是代理类中重写过的⽅法。然⽽,如果这个有注解的⽅法是被同⼀个类中的其他⽅法调⽤的,那么就不会调⽤该代理类了,⽽是直接通过当前对象去调,所以也就不⽣效了,所以我们看到的现象就是该异步⽅法没有⽣效。
具体实现原理可以看这篇⽂章
为什么⼀个⽅法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()⽅法也没有运⾏。
四、代码实践
springboot aop 根据上⾯的注意点,我们可以使⽤以下四种情况进⾏测试
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注解的⽅法...");
log.info("当前类名:{}",Class().getName());
log.info("===========================================");
log.info("2nd:调⽤其他类Test类中带有@Async注解的⽅法...");
Class c= Class();
log.info("当前类名:{}",c.getName());
log.info("Test类的⽗类:{}",c.getSuperclass().getName());
log.info("===========================================");
log.info("3rd:调⽤其他类Test1类不带@Async注解的⽅法...");
log.info("当前类名:{}",Class().getName());
log.info("===========================================");
log.info("4th:调⽤其他类Test2类(没有被springboot初始化的类)中带有Async注解的⽅法..."); Test2 test2 = new Test2();
log.info("当前类名:{}",Class().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 hod");
}
}
@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注解失败的⼏种情况以及如何解决。
⽂章参考:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论