譯文:如何運用Spring框架的@Async實現非同步任務
概要說明
在此篇文章中,我們根據使用@Async註解進行探索Spring對非同步執行的支援。
簡單的把@Async註解放到Bean的方法上就會使用不同的執行緒執行,也就是說,呼叫者執行此方法不用一直等待整個方法執行完畢。
在Spring中比較有趣的一點就是事件機制也支援非同步處理,如果你想這樣使用的話。
配置並開啟@Async掃描支援
讓我們開始使用JAVA的註解配置開啟非同步處理機制,只需要簡單的加上@EnableAsync註解到配置類上即可。
@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }
通過加上簡單的註解基本就滿足需要了,但如果你有其它需要,依然也有幾種簡單選項供你設定:
- annotation - 預設情況下, @EnableAsync 會掃描使用了Spring @Async與EJB 3.1 javax.ejb.Asynchronous的方法;此選項也可以用來掃描其他的,如使用者自定義的註解型別;
- mode - 指定應該使用哪種AOP進行切面處理 - JAVA代理或AspectJ;
- proxyTargetClass - 指定應該使用哪種代理類 - CGLIB或JDK;此屬性只有當mode設定成AdviceMode.PROXY才會產生效果。
- order
- 設定AsyncAnnotationBeanPostProcessor執行順序(生命週期有關);預設情況下會最後一個執行,所以這樣就能顧及到所有已存在的代理。
非同步處理方式配置也可以使用XML進行配置,通過使用task的namespace。
<task:executor id="myexecutor" pool-size="5" />
<task:annotation-driven executor="myexecutor"/>
關於@Async註解
首先 - 讓我們來了解一些規則 - @Async有兩點侷限性(無法正常工作)。
- 方法名必須是public進行修飾的
- 必須不能在同一個類中呼叫非同步方法
原因很簡單 - 方法名必須用public修飾才能被進行代理;而同一個類中呼叫方法的話會略過代理進行直接呼叫。
方法無返回值
下例就是一個簡單的無返回值的非同步執行方法:
@Async
public void asyncMethodWithVoidReturnType() {
System.out.println("Execute method asynchronously. "
+ Thread.currentThread().getName());
}
方法有返回值
@Async 註解的方法也可以指定返回型別 - 只需在Future的泛型中指定所需要返回的型別即可。
@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 同樣也提供了一個Future的實現類叫AsyncResult,此類可以用來跟蹤非同步方法呼叫結果。
現在,讓我們來呼叫上面的方法並通過Future進行獲取到非同步處理的結果。
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);
}
}
關於Executor
預設情況下,Spring使用SimpleAsyncTaskExecutor來執行這些非同步方法,預設的設定方式可以在兩個層級上面進行覆蓋 - 在應用全域性配置上或在單獨的方法上。
單獨的方法上覆蓋Executor
在配置類中配置所需的executor:
@Configuration
@EnableAsync
public class SpringAsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
然後,在@Async註解屬性中使用executor的名稱:
@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
System.out.println("Execute method with configured executor - "
+ Thread.currentThread().getName());
}
應用全域性配置上覆蓋Executor
配置類應該實現AsyncConfigurer介面 - 意思是getAsyncExecutor方法需要我們自己來進行實現,會返回Executor給整個應用例項使用 - 意味著現在充當預設的Executor去執行加了@Async註解的方法。
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return new ThreadPoolTaskExecutor();
}
}
異常處理
由於返回值型別是Future,異常處理就簡單了 - Future.get()會丟擲異常。
但是,如果返回型別是void,異常將無法正常傳送到呼叫的執行緒. 因此,我們需要新增一些額外的配置來處理異常。
我們建立一個實現了AsyncUncaughtExceptionHandler介面的自定義非同步異常處理類.一旦任意未捕獲的異常產生後都會呼叫handleUncaughtException()方法。
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);
}
}
}
在上一個程式碼段中,我們看到配置類實現了AsyncConfigurer介面.根據其中的部分,我們同樣也需要實現getAsyncUncaughtExceptionHandler()方法來自定義我們的非同步異常處理類:
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
結論
在本章中,我們看到利用Spring執行一個非同步任務只需要配置一些很簡單的內容就可以達到效果了,但我們同樣也看到更多高階的配置用法,如:自定義Executor或自定義異常處理策略。
然後,和往常一樣,本文中出現的程式碼在Github中可以檢視,點選這裡。