一.前言
在整理老的业务逻辑代码时候发现好多接口实现上面都标记了 @Async注解。我本身对这个注解使用的比较少,异步逻辑我都习惯自定义ThreadPoolExecutor工具类。正好借着这次梳理代码结构,来看看 @Async这个注解到底在玩什么?
本文将会给大家从 @Async注解使用层面入手逐步解读源码,分析各种踩坑实践,并且扩展sleuth链路追踪与线程变量如何花式应用。
二.尝鲜使用
Spring中,被 @Async注解标注的方法,称之为异步方法。这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作,是spring默认提供的异步调用方式。
2.1.使用方式
使用 @Async进行异步变成的方式特别简单。
- 在
启动类
或者能被启动类扫描到的配置类
上标注@EnableAsync
- 在
被spring管理的bean
的方法上标注@Async()
调用方法
与被调用方法
不在同一个bean中。
仔细品味一下上面三个限制条件,任意一个不满足,均会导致 @Async无法生效。
2.2.最简单的demo演示
启动类定义
@SpringBootApplication
@EnableAsync
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
复制代码
controller层方法定义
@RestController
@RequestMapping
public class TestController {
@Autowired
TestService testService;
@GetMapping()
public void test(){
for (int i = 0; i <5 ; i++) {
testService.testAsync();
}
}
}
复制代码
service方法定义
@Service
@Slf4j
public class TestServiceImpl implements TestService {
@Override
@Async
public void testAsync(){
log.info("嘻嘻");
}
}
复制代码
日志输出
- 2021-09-15 19:39:54.300,[http-nio-8088-exec-5], com.examp.controller.TestController - 嘿嘿
- 2021-09-15 19:36:54.302,[task-5], com.examp.service.impl.TestServiceImpl - 嘻嘻
- 2021-09-15 19:36:54.302,[task-4], com.examp.service.impl.TestServiceImpl - 嘻嘻
- 2021-09-15 19:36:54.302,[task-1], com.examp.service.impl.TestServiceImpl - 嘻嘻
- 2021-09-15 19:36:54.302,[task-2], com.examp.service.impl.TestServiceImpl - 嘻嘻
- 2021-09-15 19:36:54.302,[task-3], com.examp.service.impl.TestServiceImpl - 嘻嘻
复制代码
从日志打印可以发现,controller方法打印
与service层方法
打印日志使用的是不同的线程
。
使用是真滴简单!
2.3.踩坑提问
前面两点可以看到我们使用@Async
进行异步变成是真的简单,但是里面也埋伏了各种各样的坑点。
先抛出问题,大家可以先思考:
- 为什么阿里不推荐直接使用@Async
- @Async标注的方法是否事务一致
- 同一个类里面A->B,B方法上标注了@Async,为了调用成功,在类中注入当前类方式能否异步调用成功
- @Async标注的方法能否读取到ThreadLocal的变量
- @Async标注的方法能否获取返回值
- slueth链路追踪的traceId能否追踪到线程池内、
三.源码分析
废话不多说,看看源码。
3.1.@Async
/**
* 该注解可以标记一个异步执行的方法,也可以用来标注类,表示类中的所有方法都是异步执行的。
* 入参随意,但返回值只能是void或者Future.(ListenableFuture接口/CompletableFuture类)
* Future是代理返回的切实的异步返回,用以追踪异步方法的返回值。当然也可以使用AsyncResult类(实现
* ListenableFuture接口)(Spring或者EJB都有)或者CompletableFuture类
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
//用以限定执行方法的执行器名称(自定义):Executor或者TaskExecutor
//加在类上表示整个类都使用,加在方法上会覆盖类上的设置
String value() default "";
}
复制代码
3.2.@EnableAsync
/**
* 开启spring异步执行器,类似xml中的task标签配置,需要联合@Configuration注解一起使用,对应文章开头,注解需* 要标注在启动类或者能被启动类扫描到的配置类上。
*
* 默认情况下spring会先搜索TaskExecutor类型的bean或者名字为taskExecutor的Executor类型的bean,都不存在使* 用SimpleAsyncTaskExecutor执行器
*
* 可实现AsyncConfigurer接口复写getAsyncExecutor获取异步执行器,getAsyncUncaughtExceptionHandler获* 取异步未捕获异常处理器
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
// 该属性用来支持用户自定义异步注解,默认扫描spring的@Async和EJB3.1的@code @javax.ejb.Asynchronous
Class<? extends Annotation> annotation() default Annotation.class;
//标明是否需要创建CGLIB子类代理,AdviceMode=PROXY时才适用。注意设置为true时,其它spring管理的bean也会升级到CGLIB子类代理
boolean proxyTargetClass() default false;
//标明异步通知将会如何实现,默认PROXY,如需支持同一个类中非异步方法调用另一个异步方法,需要设置为ASPECTJ
AdviceMode mode() default AdviceMode.PROXY;
//标明异步注解bean处理器应该遵循的执行顺序,默认最低的优先级(Integer.MAX_VALUE,值越小优先级越高)
int order() default Ordered.LOW