在springboot中,提供了@Async
注解来实现异步调用,方法使用@Async
修饰之后,就变成了异步方法,这些方法将会在单独的线程被执行,方法的调用者不用在等待方法的完成。
@Async
的启用与使用
- 在配置类或者启动类上加上
@EnableAsync
注解
例如下面代码:
1 |
|
- 无返回值的方法使用
@Async
在service上方法上添加@Async
注解:
1 |
|
再在controller中调用service该方法:
1 |
|
- 有返回值的方法使用
@Async
springboot提供了AsyncResult
类来支持@Async
处理,该类实现了ListenableFuture
接口,而该接口则继承自Future
接口,所以对于需要有返回值的方法,可将返回值用Future包装,如下:
1 |
|
controller里通过future.get()获取返回值,但需要注意的是,该方法会阻塞
@Async
源码探索
我们都知道,被@Async
修饰的方法都会被单独的线程执行,而spring容器需要维护这些线程,就需要一个线程池,而默认提供的线程池就是SimpleAsyncTaskExecutor
,我们通过打印当前线程名就能看出:
1 | test:Thread[SimpleAsyncTaskExecutor-1,5,main] |
那么默认的线程池是怎么样被提供的呢,探索一下源码,这里springboot的版本是2.0.5。
首先想要使用@Async
则需要开启这个功能,即@EnbaleAsync
注解,我们看一下这个注解源码:
1 | (ElementType.TYPE) |
单从源码上,可以看到引入了一个selector:AsyncConfigurationSelector
,我们再看这个注解的注释,可以发现些端倪:
1 | * <p>By default, Spring will be searching for an associated thread pool definition: |
从注释中我们不难发现,默认情况,会去spring上下文中找TaskExecutor
、Executor
的bean或者名字是taskExecutor的bean,如果都没有,则会默认使用SimpleAsyncTaskExecutor
。从注释中我们已经可以确定了,但Spring是怎么实现的呢,@EnableAsync
中引入了AsyncConfigurationSelector
,我们点开这个类,寻找下是否有实现:
1 | public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> { |
可以看到,当是jdk代理模式时,使用ProxyAsyncConfiguration
配置类,当使用aspectJ代理时,使用org.springframework.scheduling.aspectj.AspectJAsyncConfiguration
,而默认则是jdk代理,所以继续点开ProxyAsyncConfiguration
:
1 |
|
可以看到熟悉的字眼:executor,而这里的executor来自父类AbstractAsyncConfiguration
,难道就要揭开谜底了吗,怀着激动的心情我们继续看这个父类:
1 |
|
不禁哑然,executor是通过setConfigurers()
方法赋值的,而该方法注入了AsyncConfigurer
类型的bean,通过configurer.getAsyncExecutor()
获取到executor,而可惜的是AsyncConfigurer
接口默认返回null,而它唯一的子类,也是返回null,也就是说整个spring上下文并没有该类型的bean,即executor为null,但上面我们明明通过打印日志和注释知道了默认是SimpleAsyncTaskExecutor
。
其实这里也指明了一种我们提供自定义线程池的方式,定义一个类实现
AsyncConfigurer
接口,实现getAsyncExecutor
方法返回自定义的executor,再将该类注册成bean。
那么就很明显了,@EnableAsync
这条线索是走不通了,默认线程池并不是在开启async的时候提供的,那么就有可能和@Async
注解相关。
@Async
这个注解的实现是基于spring-aop来做到的,注解源码本身并没什么,看注释:
1 | * AnnotationAsyncExecutionInterceptor |
AnnotationAsyncExecutionInterceptor
顾名思义,是@Async
注解修饰的方法执行的拦截器,该类继承自AsyncExecutionInterceptor
,类本身没什么核心方法,继续看父类AsyncExecutionInterceptor
中的核心方法invoke()
:
1 |
|
可以看到有这么一行,确定了async使用的executor:
1 | AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod); |
该方法来自AsyncExecutionInterceptor
的父类AsyncExecutionAspectSupport
,看一下该方法:
1 | protected AsyncTaskExecutor determineAsyncExecutor(Method method) { |
这里this.executor初始时是一个空的Map,初始时this.defaultExecutor也是为空,所以将会执行其中的this.defaultExecutor = getDefaultExecutor(this.beanFactory);
,而子类AsyncExecutionInterceptor
重写了该方法,再回到AsyncExecutionInterceptor
查看:
1 |
|
看到这里似乎有拨开云雾见青天的意思了,会先调用父类AsyncExecutionAspectSupport
的getDefaultExecutor()
方法,如果不为空,则返回父类获取的executor,如果为空,则返回SimpleAsyncTaskExecutor
,而父类AsyncExecutionAspectSupport
的getDefaultExecutor()
:
1 |
|
这里其实就是从Spring上下文获取TaskExecutor类型的bean,或者名称为taskExecutor的bean。
而获取到executor之后,会将被@Async修饰的方法,也就是任务task,提交(submit)到excutor中执行。
所以到这里,关于@Async
默认线程池的使用有了答案,默认会使用SimpleAsyncTaskExecutor
,而对于我们,想要自定义executor时,有两种方法,一种就是上面说的,自定义类实现AsyncConfigurer
接口再注册成bean,另一种就是注册TaskExecutor类型的bean或者名称是taskExecutor的bean。
以上探索的过程主要通过查看源码以及源码注释,以及结合debug模式下查看调用栈