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. 有返回值的方法

如果方法有返回值,需要使用 FutureCompletableFuture 包装返回值:

@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 仓库


原始标题:How To Do @Async in Spring

« 上一篇: Baeldung周报48
» 下一篇: Baeldung周报49