@Async 是 Spring 提供的一个核心注解,用于将方法调用变为异步执行。它依赖 Spring 的 AOP(面向切面编程)机制来实现。当你在一个方法上加上 @Async 时,Spring 会在运行时为该方法所在的 Bean 创建一个代理对象。所有对该方法的外部调用,实际上都是调用这个代理对象,由代理对象负责将方法的执行提交给一个线程池,从而实现异步效果。
基于这个原理,我们可以总结出 @Async 失效的几种典型场景。
这是 最常见 的失效场景。
原因:
Spring 的 AOP 是基于代理实现的。只有通过 Spring 容器获取到的 Bean(即代理对象)去调用 @Async 方法时,AOP 切面才能生效。如果在同一个类的内部,一个方法直接调用另一个被 @Async 标注的方法,这个调用是对象内部的直接方法调用,没有经过 Spring 生成的代理对象,因此异步注解会失效,方法仍然会同步执行。
实例:
@Service
public class MyService {
public void doSomething() {
// 内部调用,不会异步执行
this.asyncMethod();
}
@Async
public void asyncMethod() {
System.out.println("执行异步任务,线程:" + Thread.currentThread().getName());
}
}
解决方案:
确保调用是通过 Spring 容器的 Bean 进行的。
@Service
public class MyService {
@Autowired
private AsyncTaskService asyncTaskService;
public void doSomething() {
// 调用另一个 Bean 的方法,异步生效
asyncTaskService.asyncMethod();
}
}
@Service
public class AsyncTaskService {
@Async
public void asyncMethod() {
System.out.println("执行异步任务,线程:" + Thread.currentThread().getName());
}
}
在方法内部从 Spring 容器中获取当前 Bean 的代理对象,再用这个代理对象去调用方法。
@Service
public class MyService implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void doSomething() {
// 获取代理对象
MyService proxyService = applicationContext.getBean(MyService.class);
proxyService.asyncMethod(); // 异步生效
}
@Async
public void asyncMethod() {
System.out.println("执行异步任务,线程:" + Thread.currentThread().getName());
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
原因:
Spring 的 AOP 代理(无论是 JDK 动态代理还是 CGLIB 代理)都要求目标方法必须是 public。这是 Java 语言的访问限制和代理机制所决定的。如果方法是 private、protected 或 default 访问级别,代理无法对其进行拦截,@Async 自然失效。
实例:
@Service
public class MyService {
// 方法是 private,@Async 失效
@Async
private void asyncMethod() {
System.out.println("执行异步任务");
}
}
解决方案:
原因:
@Async 的异步功能是由 Spring 的容器在初始化 Bean 时,通过 AOP 动态织入的。如果这个类没有被声明为 Spring 的 Bean(例如,没有使用 @Component, @Service, @Controller 等注解,或者没有在 XML 中配置),那么 Spring 就不会为其创建代理,@Async 也就无从谈起。
实例:
// 这个类不是 Spring Bean
public class MyService {
@Async
public void asyncMethod() {
System.out.println("执行异步任务");
}
}
// 在另一个类中使用 new 关键字创建对象
@Service
public class AnotherService {
public void test() {
MyService service = new MyService(); // 非Spring管理的对象
service.asyncMethod(); // @Async 失效
}
}
解决方案:
原因:
Spring Boot 默认是不开启 @Async 注解的处理的。你必须在配置类或者启动类上显式地添加 @EnableAsync 注解来启用它。否则,Spring 会忽略所有的 @Async 注解,方法会以同步方式执行。
实例:
// 没有添加 @EnableAsync
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
解决方案:
@SpringBootApplication
@EnableAsync // 启用异步功能
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
原因:
static 方法: 静态方法属于类本身,不属于某个对象。Spring 的 AOP 是基于对象的代理,因此无法对静态方法进行拦截和增强。
final 方法: 如果目标对象是通过 CGLIB 动态生成子类来实现代理的,那么代理类需要覆盖目标方法。final 方法不能被子类覆盖,所以 CGLIB 无法代理它。如果是 JDK 动态代理,虽然代理的是接口,但被代理的实现类中的 final 方法同样无法被代理逻辑替换。
实例:
@Service
public class MyService {
// static 方法,@Async 失效
@Async
public static void staticAsyncMethod() {
System.out.println("静态方法执行异步任务");
}
// final 方法,@Async 失效
@Async
public final void finalAsyncMethod() {
System.out.println("Final 方法执行异步任务");
}
}
解决方案:
原因:
当 @Async 方法返回值为 void 时,如果方法内部抛出了异常,这个异常无法被调用者捕获,因为调用者在方法“提交”到线程池后就已经返回了。异常会被 Spring 的 AsyncUncaughtExceptionHandler 捕获并打印到日志中(默认行为)。从调用者的角度看,方法似乎“悄无声息”地失败了,这可能被误认为是“失效”。
实例:
@Service
public class MyService {
@Async
public void asyncMethodWithException() {
System.out.println("执行一个会抛出异常的异步任务");
throw new RuntimeException("异步任务内部发生错误");
}
}
@Service
public class AnotherService {
@Autowired
private MyService myService;
public void test() {
try {
myService.asyncMethodWithException();
System.out.println("调用者:我以为异步任务成功了!");
} catch (Exception e) {
// 这里捕获不到异常
e.printStackTrace();
}
}
}
解决方案:
@Service
public class MyService {
@Async
public CompletableFuture<String> asyncMethodWithResult() {
System.out.println("执行异步任务");
if (true) {
throw new RuntimeException("异步任务出错");
}
return CompletableFuture.completedFuture("任务成功");
}
}
@Service
public class AnotherService {
@Autowired
private MyService myService;
public void test() {
CompletableFuture<String> future = myService.asyncMethodWithResult();
try {
String result = future.get(); // 这里会抛出 ExecutionException
System.out.println("异步任务结果: " + result);
} catch (Exception e) {
System.err.println("调用者捕获到异常: " + e.getMessage());
}
}
}
总结
@Async 失效的根本原因是 Spring 的 AOP 代理没有生效。请记住以下几点,以确保 @Async 正常工作:
必须是 public 方法。
必须通过 Spring 容器管理的 Bean 调用。
不能在同类中直接调用(自调用)。
必须在配置类上添加 @EnableAsync。
方法不能是 static 或 final。
为了能捕获异常,推荐返回 Future 或 CompletableFuture。