SpringBoot @Async 异步注解在什么场景会失效
SpringBoot @Async 异步注解在什么场景会失效
@Async的底层原理是 Spring AOP 动态代理——Spring 会生成目标类的代理对象,调用方通过代理对象调用方法时,代理会将任务提交到线程池异步执行。因此,凡是绕过代理的场景,@Async就会失效。
1. 未开启异步支持
没有在配置类上添加
@EnableAsync,Spring 不会扫描@Async注解,注解形同虚设。
@SpringBootApplication
@EnableAsync // 必须加,否则 @Async 全部失效
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. 同类内部调用(最常见)
同一个类中,方法 A 直接调用方法 B(
this.methodB()),走的是原始对象引用,不经过代理,@Async失效。
@Service
public class OrderService {
public void createOrder() {
sendNotification(); // 直接调用 this.sendNotification(),不走代理,@Async 失效!
}
@Async
public void sendNotification() {
// 期望异步执行,实际同步执行
}
}
解决方案: 将异步方法抽取到另一个 Bean 中,或通过
ApplicationContext/AopContext获取代理对象再调用。
// 方案一:抽取到独立 Bean(推荐)
@Service
@RequiredArgsConstructor
public class OrderService {
private final NotificationService notificationService;
public void createOrder() {
notificationService.sendNotification(); // 通过代理调用,@Async 生效
}
}
// 方案二:通过 AopContext 获取当前代理(需开启 exposeProxy = true)
@EnableAsync(proxyTargetClass = true)
// 同时在 @EnableAspectJAutoProxy 上设置 exposeProxy = true
@Service
public class OrderService {
public void createOrder() {
((OrderService) AopContext.currentProxy()).sendNotification();
}
@Async
public void sendNotification() { }
}
3. 方法不是 public
Spring AOP 默认只代理
public方法。@Async标注在private、protected或包级私有方法上,代理无法拦截,注解失效。
@Service
public class ReportService {
@Async
private void generateReport() { // private 方法,@Async 失效!
// ...
}
}
解决方案: 将方法改为
public。
4. Bean 未被 Spring 管理
@Async依赖 Spring 容器创建代理,如果类通过new关键字手动创建,不在容器中,代理不会生成,@Async失效。
// 错误:手动 new,不走 Spring 容器
OrderService service = new OrderService();
service.sendNotification(); // @Async 失效
// 正确:从容器中获取
@Autowired
private OrderService orderService;
orderService.sendNotification(); // @Async 生效
5. 异步方法返回值类型不正确
@Async方法的返回值只能是void或Future(Future、CompletableFuture、ListenableFuture)。返回其他类型时,Spring 不会抛出异常,但异步执行可能出现非预期行为。
@Async
public String getData() { // 返回 String,行为不确定,不推荐
return "result";
}
// 正确写法
@Async
public CompletableFuture<String> getData() {
return CompletableFuture.completedFuture("result");
}
@Async
public void fireAndForget() { // void 也是合法的
// ...
}
6. 使用 @Transactional 的方法上叠加 @Async
@Async会将方法切换到新线程执行,而@Transactional的事务绑定在当前线程的ThreadLocal上,线程切换后事务上下文丢失,事务不会生效(不是@Async失效,而是事务失效)。
@Async
@Transactional // 事务不生效!新线程中没有事务上下文
public void asyncWithTransaction() {
userRepository.save(user);
}
解决方案: 在异步方法内部通过
@Transactional的嵌套方法重新开启事务,或使用编程式事务TransactionTemplate。
@Async
public void asyncTask() {
doWithTransaction(); // 在异步线程内调用有事务的方法
}
@Transactional
public void doWithTransaction() {
userRepository.save(user);
}
7. 未配置线程池,默认线程池被耗尽
未指定线程池时,
@Async默认使用SimpleAsyncTaskExecutor,该执行器每次都新建线程,不复用,高并发下会耗尽资源,甚至导致 OOM。虽然@Async不会”失效”,但会引发严重的性能问题。
// 推荐:显式配置线程池
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
也可以在
@Async中指定具体线程池:
@Async("myExecutor") // 指定名称为 myExecutor 的线程池 Bean
public void specificPoolTask() { }
8. 循环依赖导致代理失效
当异步 Bean 存在循环依赖时,Spring 可能提前暴露原始对象(未被代理),导致注入的是原始对象而非代理对象,
@Async失效。启动时通常会看到类似警告:
Bean with name 'xxx' has been injected into other beans [...] in its raw version as part of a circular reference
解决方案: 消除循环依赖,或用
@Lazy延迟注入打破循环。
失效场景汇总
| 场景 | 根本原因 | 解决方案 |
|---|---|---|
未加 @EnableAsync
|
未开启 AOP 拦截 | 配置类加 @EnableAsync
|
| 同类内部调用 | 调用走 this,绕过代理 |
抽取到独立 Bean |
| 方法非 public | AOP 无法拦截 | 改为 public
|
手动 new 对象 |
不在 Spring 容器中 | 通过 @Autowired 注入 |
| 返回值类型错误 | 不符合 @Async 规范 |
改为 void 或 Future
|
叠加 @Transactional
|
线程切换丢失事务上下文 | 在异步方法内嵌套事务方法 |
| 未配置线程池 | 默认每次新建线程 | 实现 AsyncConfigurer 配置线程池 |
| 循环依赖 | 注入了原始对象而非代理 | 消除循环依赖或 @Lazy
|