一、背景
對於一些的耗時的且與處理結果業務不是緊密關聯的,我們採用異步調用的方式處理。一般我們會手動創建一個線程池,來執行這個耗時的異步任務。其實spring 已經提供了一個註解來幫我們幹了這件事了
二、使用方式
使用方式就是很簡單了
1、在啓動類中加入@EnableAsync 是異步調用 @Async註解生效
2、在需要異步執行的方法上加上@Async,也可以在類上面加,表示該類中的所有的方法都是需要異步執行的
注意點:
1、默認情況情況下使用的是這個SimpleAsyncTaskExecutor 線程池,這個並不是真正意義的線程池,線程是不重用的,每個任務來都會創建一個新的線程。有可能導致OOM問題,所以建議自定義線程池,通過實現AsyncConfigurer 類,重寫getAsyncExecutor 方法。代碼如下:
@Slf4j
@Component
public class MyAsyncConfigurer implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ExecutorService service = Executors.newFixedThreadPool(10);
return service;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new MyAsyncExceptionHandler();
}
class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
log.info("Exception message - " + throwable.getMessage());
log.info("Method name - " + method.getName());
for (Object param : objects) {
log.info("Parameter value - " + param);
}
}
}
}
2、調用的異步方法不能是同一個類的方法。原因是@Async註解本質上是使用的動態代理,spring在啓動掃描時,將含有AOP註解的類和對象替換成代理類,而同類調用時,還是調用的是對象本身,並不是代理類。 所以會導致@Async失效。同樣的問題@Transaction、@cache 也有。
解決這問題可以將異步的方法放到一個單獨的類中,這個類加上@compent 交給spring 管理。或者我們可以通過spring的上下文來獲取代理對象。詳細代碼:
@RestController
@RequestMapping("/api")
@Slf4j
public class ApiController {
@Resource
private ApplicationContext applicationContext;
@RequestMapping("/asynCall")
public Object asynCall() {
try {
ApiController apiController = applicationContext.getBean(ApiController.class);
for (int i= 0;i<10;i++) {
Future future = apiController.testAsynTask(i);
}
return "success";
} catch (Exception e) {
return "error";
}
}
@Async
public Future testAsynTask(int i ) throws InterruptedException {
Thread.sleep(10000);
System.out.println("異步任務執行完成..,"+i+","+Thread.currentThread().getName());
Future future = new AsyncResult("ok");
return future;
}}