1. 概述
在本文中,我们将深入探讨 Spring 框架中的异步执行支持,特别是 @Async 注解的使用。
简单来说,当我们在一个 Bean 的方法上加上 @Async 注解时,该方法将会在 一个独立线程中执行,调用者不会等待该方法执行完成。
值得一提的是,Spring 框架本身对异步处理的支持不仅限于 @Async,其 事件机制 也支持异步处理,如有需要可以结合使用。
2. 启用异步支持
首先,我们需要在配置类上添加 @EnableAsync 注解来启用异步处理功能。
使用 Java 配置方式如下:
@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }
这个注解本身已经足够启用异步支持,但你也可以通过一些参数进行更细粒度的配置:
- annotation:默认识别 Spring 的 @Async 和 EJB 的 javax.ejb.Asynchronous。你可以自定义识别的注解类型
- mode:指定使用 JDK 动态代理还是 AspectJ 织入
- proxyTargetClass:指定使用 CGLIB 还是 JDK 代理(仅在 mode 为 AdviceMode.PROXY 时有效)
- order:设置 AsyncAnnotationBeanPostProcessor 的执行顺序,默认为最后执行,以便兼容其他代理
✅ XML 配置方式也可以启用异步支持,使用 <task>
命名空间:
<task:executor id="myexecutor" pool-size="5" />
<task:annotation-driven executor="myexecutor"/>
3. @Async 注解详解
使用 @Async 时需要注意以下两点:
- 只能用于 public 方法
- 不能通过 self-invocation(本类内部调用)触发异步执行
⚠️ 原因很简单:Spring 使用代理机制实现异步调用,私有方法无法被代理,而 self-invocation 会绕过代理直接调用原方法,导致异步失效。
3.1. void 返回类型方法
这是最简单的异步方法定义方式:
@Async
public void asyncMethodWithVoidReturnType() {
System.out.println("Execute method asynchronously. "
+ Thread.currentThread().getName());
}
3.2. 有返回值的方法
如果方法有返回值,需要使用 Future 或 CompletableFuture 包装返回值:
@Async
public Future<String> asyncMethodWithReturnType() {
System.out.println("Execute method asynchronously - "
+ Thread.currentThread().getName());
try {
Thread.sleep(5000);
return new AsyncResult<String>("hello world !!!!");
} catch (InterruptedException e) {
//
}
return null;
}
Spring 提供了 AsyncResult 类来封装异步结果。你可以通过下面方式调用并获取结果:
public void testAsyncAnnotationForMethodsWithReturnType()
throws InterruptedException, ExecutionException {
System.out.println("Invoking an asynchronous method. "
+ Thread.currentThread().getName());
Future<String> future = asyncAnnotationExample.asyncMethodWithReturnType();
while (true) {
if (future.isDone()) {
System.out.println("Result from asynchronous process - " + future.get());
break;
}
System.out.println("Continue doing something else. ");
Thread.sleep(1000);
}
}
3.3. 合并两个 @Async 接口的响应
通过 CompletableFuture 可以优雅地合并多个异步接口的结果:
@Async
public CompletableFuture<String> asyncGetData() throws InterruptedException {
System.out.println("Execute method asynchronously " + Thread.currentThread()
.getName());
Thread.sleep(4000);
return new AsyncResult<>(super.getClass().getSimpleName() + " response !!! ").completable();
}
接着定义一个服务类合并两个异步接口的返回值:
@Service
public class AsyncService {
@Autowired
private FirstAsyncService fisrtService;
@Autowired
private SecondAsyncService secondService;
public CompletableFuture<String> asyncMergeServicesResponse() throws InterruptedException {
CompletableFuture<String> fisrtServiceResponse = fisrtService.asyncGetData();
CompletableFuture<String> secondServiceResponse = secondService.asyncGetData();
return fisrtServiceResponse.thenCompose(fisrtServiceValue ->
secondServiceResponse.thenApply(secondServiceValue ->
fisrtServiceValue + secondServiceValue));
}
}
调用方式如下:
public void testAsyncAnnotationForMergedServicesResponse() throws InterruptedException, ExecutionException {
System.out.println("Invoking an asynchronous method. " + Thread.currentThread()
.getName());
CompletableFuture<String> completableFuture = asyncServiceExample.asyncMergeServicesResponse();
while (true) {
if (completableFuture.isDone()) {
System.out.println("Result from asynchronous process - " + completableFuture.get());
break;
}
System.out.println("Continue doing something else. ");
Thread.sleep(1000);
}
}
4. 线程池(Executor)
默认情况下,Spring 使用 SimpleAsyncTaskExecutor 来执行异步方法。但在实际项目中,建议使用线程池来提高性能和资源管理。
4.1. 方法级别指定线程池
在配置类中定义一个线程池:
@Configuration
@EnableAsync
public class SpringAsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
然后在方法上指定使用该线程池:
@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
System.out.println("Execute method with configured executor - "
+ Thread.currentThread().getName());
}
4.2. 全局默认线程池
如果希望为整个应用统一指定一个线程池,可以在配置类实现 AsyncConfigurer 接口:
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return new ThreadPoolTaskExecutor();
}
}
这样,所有未显式指定线程池的 @Async 方法都会使用这个全局线程池。
5. 异常处理
当方法返回值为 Future 类型时,异常可以通过 Future.get() 捕获。但如果返回值是 void,异常将不会自动传播到主线程。
为此,我们需要自定义异常处理器,实现 AsyncUncaughtExceptionHandler 接口:
public class CustomAsyncExceptionHandler
implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(
Throwable throwable, Method method, Object... obj) {
System.out.println("Exception message - " + throwable.getMessage());
System.out.println("Method name - " + method.getName());
for (Object param : obj) {
System.out.println("Parameter value - " + param);
}
}
}
然后在配置类中返回这个处理器:
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
这样,所有未捕获的异步异常都会被这个处理器捕获,便于统一处理。
6. 总结
本文介绍了如何在 Spring 中使用 @Async 实现异步调用,包括:
- 启用异步支持(Java & XML 配置)
- @Async 的使用限制和返回值处理
- 多个异步任务结果的合并
- 自定义线程池配置
- 异步异常处理
通过这些内容,你可以灵活地在项目中使用异步编程,提高系统响应能力和吞吐量。
完整的代码示例可以参考 GitHub 仓库。