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 - asyncMethodWithExceptions6. 結論
在本文中,我們探討了使用 Spring 7 和 Spring Boot 4 運行異步代碼的方法。我們採用現代方法,使用 CompletableFuture 作為返回值類型,並研究了核心配置,包括使用 @EnableAsync 和 AsyncConfigurer,以及自定義執行器和異常處理策略。