@Async在之前的文章Spring Event里介绍过(https://www.jianshu.com/p/6799984d2093)3.2章。位于spring-context包中,3.1版本开始支持。
@Async的主要作用是支持异步调用方法,他会单独跑在另一个线程中,所以调用方无需等待Async方法,节省了时间。常用的场景如:通知,发邮件等。
文章内容:

1. 简单demo
@EnableAsync,表示项目开始异步方法调用,该注解有一些参考,下文有介绍。
@SpringBootApplication
@EnableAsync
public class SpringProjectApplication {
public static void main(String[] args) {
SpringApplication.run(SpringProjectApplication.class, args);
}
}
AsyncOrderService是调用方。
AsyncOrderNotifyService提供异步方法。
@Slf4j
@Service
public class AsyncOrderService {
@Autowired
private AsyncOrderNotifyService asyncOrderNotifyService;
public void saveOrderAndNotify() {
asyncOrderNotifyService.notifyUser();
log.info("end save order...");
}
}
@Slf4j
@Service
public class AsyncOrderNotifyService {
@Async
public void notifyUser() {
ThreadUtils.sleep(3000L);
log.info("finished notifying...");
}
}
测试方法:
@Test
public void saveOrderAndNotify() {
asyncOrderService.saveOrderAndNotify();
ThreadUtils.sleep(5000L); // wait for async method finish
}
测试Log: 可以看出Notify方法的线程名称不是main,并且OrderService并没有等Notify结果,而是先执行完毕了。
2022-04-19 13:54:36.209 INFO 24992 --- [ main] AsyncOrderService : end save order...
2022-04-19 13:54:39.218 INFO 24992 --- [ task-1] AsyncOrderNotifyService : finished notifying...
2. @EnableAsync配置详解
这里特别要注意的就是配置proxyTargetClass影响的是Spring所有的代理,会影响@Transactional那块的功能。

举例:可自定义CustomAsync注解,用法同@Async一样:
注:如果配置了annotation类,那么原来的@Async注解会失效。
@EnableAsync(annotation = CustomAsync.class)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomAsync {
}
3. 重点介绍@Async
3.1 @Async注解不起作用的情况
由于用到了proxy,所以思路与Transactional注解失效思路差不多:
- 只针对public方法有效,private方法IDE也会提示报错。
- 只自己调用自己的方法也会无效。
举例:
@Slf4j
@Service
public class AsyncOrderService {
public void saveOrderAndNotify() {
notifyUser();
log.info("end save order...");
}
@Async
public void notifyUser() {
ThreadUtils.sleep(3000L);
log.info("finished notifying...");
}
}
测试log:都在主线程下执行,并没有异步:
2022-04-19 14:59:15.049 INFO 25118 --- [ main] AsyncOrderService : finished notifying...
2022-04-19 14:59:15.050 INFO 25118 --- [ main] AsyncOrderService : end save order...
3.2 @Async无返回值
第1章简单demo中的例子就是无返回值的@Async方法。
3.3 @Async有返回值
这里可以理解为:
- 调用者调用了异步方法。
- 不需要等待异步方法执行,而是自己执行自己的代码逻辑。
- 最后再拿异步方法的返回值(节省了时间)
举例:先是异步方法有返回值,需要用Future类封装:
@Async
public Future<Boolean> notifyUserAndReturn() {
ThreadUtils.sleep(3000L);
log.info("finished notifying and return true...");
return new AsyncResult<>(true);
}
调用方调用异步方法,以及拿到异步方法执行结果:
public void saveOrderAndNotifyWithReturn() {
Future<Boolean> future = asyncOrderNotifyService.notifyUserAndReturn();
log.info("end save order...");
try {
while (true) {
if (future.isDone()) {
boolean result = future.get().booleanValue();
log.info("get notify result : {}", result);
break;
}
}
} catch (InterruptedException | ExecutionException e) {
log.error("met exception.", e);
}
}
测试log:
2022-04-19 15:13:04.785 INFO 25124 --- [ main] AsyncOrderService : end save order...
2022-04-19 15:13:07.789 INFO 25124 --- [ task-1] AsyncOrderNotifyService : finished notifying and return true...
2022-04-19 15:13:07.791 INFO 25124 --- [ main] AsyncOrderService : get notify result : true
Future的异常处理(多线程的内容):
如果上述方法notifyUserAndReturn()报IllegalArgumentException异常,那么在调用方的代码中,会在future.get()方法中抛出。即在future.get().booleanValue()抛出。
4. Executor(重要):
4.1 异步方法执行需要另外的线程,Spring如何找线程池?
- Spring会搜索TaskExecutor接口的bean。
- 如果没有找到,则会找name=“taskExecutor"的bean。
- 如果上述1# 2#都没有,那么会自定义一个SimpleAsyncTaskExecutor的bean。不推荐,主要原因有:
3.1 关于SimpleAsyncTaskExecutor的实现,不能很好的返回Exception异常。
3.2 每次都为task新创建一个线程,不能复用线程。如果想要更好的管理线程,可以考虑自己实现一个TaskExecutor。
4.2 方法层自定义线程池:
@Configuration
public class AsyncConfig {
@Bean(name = "testThreadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 线程名前缀:
executor.setThreadNamePrefix("Test Pool - ");
return executor;
}
}
Spring支持多个Executor bean(看上述AsyncConfig配置),异步方法可以指定使用哪个线程池:
@Slf4j
@Service
public class AsyncOrderNotifyService {
// 指定使用哪个线程池:
@Async("testThreadPoolTaskExecutor")
public void notifyUser() {
ThreadUtils.sleep(3000L);
log.info("finished notifying...");
}
}
测试log:
2022-04-19 21:08:38.772 INFO 25385 --- [ main] AsyncOrderService : end save order...
2022-04-19 21:08:41.779 INFO 25385 --- [ Test Pool - 1] AsyncOrderNotifyService : finished notifying...
4.3 全局定义:
实现AsyncConfigurer接口,可以在方法getAsyncExecutor()自定义线程池:
@Configuration
public class GlobalAsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("Global Pool - ");
executor.initialize();
return executor;
}
}
测试log:
2022-04-19 21:51:28.500 INFO 25470 --- [ main] AsyncOrderService : end save order...
2022-04-19 21:51:31.505 INFO 25470 --- [Global Pool - 1] AsyncOrderNotifyService : finished notifying...
5. 异常处理
5.1 返回值类型是Future:
在上述3.3所述,由于返回值类型是Future,异常处理就简单了:Future.get()会抛出异常。
5.2 自定义ExceptionHandler:
实现AsyncUncaughtExceptionHandler接口:
在实现AsyncConfigurer后,可以选择实现接口方法getAsyncExecutor()以及方法getAsyncUncaughtExceptionHandler(),因为这两个方法的修饰符是default,当然也可以自定义AsyncUncaughtExceptionHandler类:
@Configuration
public class GlobalAsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
// 同上
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
}
@Slf4j
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... params) {
log.info("Exception message: " + throwable.getMessage());
log.info("Method name: " + method.getName());
for (Object param : params) {
log.info("Parameter value: " + param);
}
}
}
测试,异步方法主动报错:
@Async
public void notifyUser() {
ThreadUtils.sleep(3000L);
if (1 == 1) {
// 主动抛错:
throw new IllegalArgumentException("illegal argument.");
}
log.info("finished notifying...");
}
测试log:可以看到异常已经被自定义的CustomAsyncExceptionHandler捕获:
2022-04-19 22:06:12.278 INFO 25478 --- [ main] AsyncOrderService : end save order...
2022-04-19 22:06:15.280 INFO 25478 --- [Global Pool - 1] CustomAsyncExceptionHandler : Exception message: illegal argument.
2022-04-19 22:06:15.280 INFO 25478 --- [Global Pool - 1] CustomAsyncExceptionHandler : Method name: notifyUser
参考:
[baeldung] Spring Async:https://www.baeldung.com/spring-async#enable-async-support
Spring-boot异步(写的很好的文章):https://mongoding.github.io/2017/08/22/springboot-%E5%BC%82%E6%AD%A5/
ThreadPoolTaskScheduler not initialized问题分析:https://blog.csdn.net/blueheart20/article/details/103061426
网友评论