知識庫 / Spring RSS 訂閱

使用 Spring 中的 @Async 詳解

Spring
HongKong
5
03:01 PM · Dec 06 ,2025

1. 概述

本教程將探討 Spring 中的 異步執行支持以及 @Async 註解,並採用現代 Java 和 Spring 7 的實踐。

簡單來説,使用 @Async 註解標記一個 Bean 的方法將會在一個獨立的線程中執行該方法。換句話説,調用者將不會等待被調用方法完成,從而使應用程序更具響應性和效率。

Spring 的一個有趣方面是,框架中的事件支持也 支持異步處理(如果需要)。

2. 啓用異步支持

讓我們首先通過使用 Java 配置啓用異步處理。

我們將通過在配置類中添加 <em @EnableAsync</em>> 來實現。

@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }

儘管 @EnableAsync 註解已經足夠,但還有一些簡單的配置選項:

  • annotation – 用於檢測除了 Spring 的 @Async 之外的其他用户自定義註解類型,用於異步執行。
  • mode – 指示使用的建議類型(基於 JDK 代理或 AspectJ 編織)。
  • proxyTargetClass – 指示使用的代理類型(CGLIB 或 JDK)。僅在 mode 設置為 AdviceMode.PROXY 時才有效。
  • order – 設置 AsyncAnnotationBeanPostProcessor 應應用的順序。默認情況下,它會運行在最後,以便可以考慮所有現有的代理。

請注意,在現代 Spring Boot 4 應用程序中,使用 XML 配置啓用異步支持通常應避免,而是應使用 Java 配置。

3. @Async 註解

首先,我們來了解 @Async 的兩個主要限制:

  • 必須應用於 public 方法。
  • 自調用—在同一類中調用異步方法—將不起作用,因為它繞過了攔截異步執行調用的 Spring 代理。

原因很簡單。方法必須是 public 才能被代理。 自調用 不起作用,因為它繞過代理並直接調用底層方法。

3.1. 使用 void 返回類型的函數

這是配置不需返回任何值的函數以進行異步運行的簡單方法:

@Async
public void asyncMethodWithVoidReturnType() {
    System.out.println("Execute method asynchronously. " 
      + Thread.currentThread().getName());
}

由於此操作是void,我們通常認為調用線程會立即繼續,但對於簡單的集成測試,調用它已經足夠了:

@Autowired
private AsyncComponent asyncAnnotationExample; 

@Test
public void testAsyncAnnotationForMethodsWithVoidReturnType() {
    asyncAnnotationExample.asyncMethodWithVoidReturnType();
}

3.2. 帶有返回類型的操作方法:使用 CompletableFuture

對於帶有返回類型的操作方法,Spring 7 和 Spring Boot 4 強烈建議使用 CompletableFuture。 此外,較舊的 AsyncResult 已被棄用。

通過返回一個 CompletableFuture,我們獲得了強大的組合和鏈式調用能力,使得異步操作更容易管理:

@Async
public CompletableFuture<String> asyncMethodWithReturnType() {
    System.out.println("Execute method asynchronously - " 
      + Thread.currentThread().getName());
    try {
        Thread.sleep(5000);
        return CompletableFuture.completedFuture("hello world !!!!");
    } catch (InterruptedException e) {
        return CompletableFuture.failedFuture(e);
    }
}

現在,讓我們通過使用 CompletableFuture 對象來調用該方法並檢索結果:

@Autowired 
private SimpleAsyncService simpleAsyncService;

@Test
public void testAsyncAnnotationForMethodsWithReturnType()
  throws InterruptedException, ExecutionException {
 
    CompletableFuture<String> future = simpleAsyncService.asyncMethodWithReturnType();
    System.out.println("Invoking an asynchronous method. " 
      + Thread.currentThread().getName());
    
    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</em/> 服務的響應

本示例演示瞭如何使用 CompletableFuture</em/> 方法將兩個單獨的異步服務調用的結果合併。 讓我們定義兩個服務類,FirstAsyncService</em/> 和 SecondAsyncService</em/>,它們具有帶有 @Async</em/> 註解的方法:

@Async
public CompletableFuture<String> asyncGetData() throws InterruptedException {
    Thread.sleep(4000);
    return CompletableFuture.completedFuture(
        super.getClass().getSimpleName() + " response !!! "
    );
}

我們現在正在實施一個主要服務,用於合併兩個 <em @Async 服務的 <em CompletableFuture 響應:

@Service
public class AsyncService {

    @Autowired
    private FirstAsyncService firstService;
    @Autowired
    private SecondAsyncService secondService;

    public CompletableFuture<String> asyncMergeServicesResponse() throws InterruptedException {
        CompletableFuture<String> firstServiceResponse = firstService.asyncGetData();
        CompletableFuture<String> secondServiceResponse = secondService.asyncGetData();

        return firstServiceResponse.thenCompose(
          firstServiceValue -> secondServiceResponse.thenApply(
            secondServiceValue -> firstServiceValue + secondServiceValue));
    }
}

讓我們調用上述服務並使用 CompletableFuture 對象檢索異步服務的結果:

@Autowired
private AsyncService asyncServiceExample;

@Test
public void testAsyncAnnotationForMergedServicesResponse()
  throws InterruptedException, ExecutionException {
    CompletableFuture<String> completableFuture = asyncServiceExample
        .asyncMergeServicesResponse();

    System.out.println("Invoking asynchronous methods. " + Thread.currentThread().getName());

    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);
    }
}

讓我們檢查 <em>AsyncServiceUnitTest</em> 集成測試類對合並服務響應的輸出:

Invoking asynchronous methods. main
Continue doing something else.
Continue doing something else.
Continue doing something else.
Continue doing something else.
Result from asynchronous process - FirstAsyncService response !!! SecondAsyncService response !!!

4. 執行器

默認情況下,Spring 使用 SimpleAsyncTaskExecutor 來異步執行這些方法。這在開發階段是合適的;但是,對於生產環境,我們應該配置一個合適的線程池,例如 ThreadPoolTaskExecutor,以管理資源消耗。

我們可以通過以下兩個級別覆蓋默認設置:應用程序級別或單個方法級別。

4.1. 在方法級別覆蓋執行器

我們需要在配置類中聲明所需的執行器作為 Spring Bean:

@Configuration
@EnableAsync
public class SpringAsyncConfig {
    
    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("CustomPool-");
        executor.initialize();
        return executor;
    }
}

然後,我們需要將執行器名稱作為 @Async 註解的屬性提供:

@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
    System.out.println("Execute method with configured executor - "
      + Thread.currentThread().getName());
}

4.2. 在應用程序級別覆蓋執行器

為此,配置類應實現 <em >AsyncConfigurer</em> 接口。 從而強制它實現 <em >getAsyncExecutor()</em> 方法,該方法將返回所有帶有 <em >@Async</em> 註解的應用程序方法(除非在方法級別進行覆蓋)的默認執行器:

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
   
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.initialize();
        return executor;
    }
    
    // ...
}

5. 異常處理

當一個方法返回一個 <em>CompletableFuture</em> 時,異常處理非常簡單。在異步方法內部拋出的異常會導致 <em>CompletableFuture</em> 以異常方式完成,並且任何後續的 <em>then…</em> 階段都會處理它,或者調用 <em>future.get()</em> 會拋出包裝的異常 (<em>ExecutionException</em>)。

但是,如果方法返回類型是 <em>void</em>,則異常不會被傳播回調用線程。對於這種情況,我們必須註冊一個自定義處理器

讓我們通過實現 <em>AsyncUncaughtExceptionHandler</em> 創建一個自定義異步異常處理器:

public class CustomAsyncExceptionHandler
  implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
        System.err.println("Async Exception Detected!");
        System.err.println("Exception message - " + throwable.getMessage());
        System.err.println("Method name - " + method.getName());
        for (Object param : obj) {
            System.err.println("Parameter value - " + param);
        }
    }
}

最後,讓我們通過在 AsyncConfigurer 實現中覆蓋 getAsyncUncaughtExceptionHandler() 方法來註冊此處理程序:

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
    // ... getAsyncExecutor() implementation ...

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }
}

讓我們檢查一下 AsyncAnnotationExampleIntegrationTest 這個集成測試類的輸出:

Invoking an asynchronous method. main
Continue doing something else.
Execute method asynchronously - DefaultAsync-1
Continue doing something else.
Continue doing something else.
Continue doing something else.
Continue doing something else.
Result from asynchronous process - hello world !!!!
Execute method with configured executor - CustomPool-1
Execute method asynchronously. DefaultAsync-2
Async Exception Detected!
Exception message - Throw message from asynchronous method.
Method name - asyncMethodWithExceptions

6. 結論在本文中,我們探討了使用 Spring 7 和 Spring Boot 4 運行異步代碼的方法。

我們採用現代方法,使用 CompletableFuture 作為返回值類型,並研究了核心配置,包括使用 @EnableAsyncAsyncConfigurer,以及自定義執行器和異常處理策略。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.