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 标注在 privateprotected 或包级私有方法上,代理无法拦截,注解失效。

@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 方法的返回值只能是 voidFutureFutureCompletableFutureListenableFuture)。返回其他类型时,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 规范 改为 voidFuture
叠加 @Transactional 线程切换丢失事务上下文 在异步方法内嵌套事务方法
未配置线程池 默认每次新建线程 实现 AsyncConfigurer 配置线程池
循环依赖 注入了原始对象而非代理 消除循环依赖或 @Lazy
本文作者:
本文链接: https://hgnulb.github.io/blog/2026/66816195
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!