原文地址:http://blog.jboost.cn/springboot-async.htmlhtml
在業務開發中,有時候會遇到一些非核心的附加功能,好比短信或微信模板消息通知,或者一些耗時比較久,但主流程不須要當即得到其結果反饋的操做,好比保存圖片、同步數據到其它合做方等等。若是將這些操做都置於主流程中同步處理,勢必會對核心流程的性能形成影響,甚至因爲第三方服務的問題致使自身服務不可用。這時候就應該將這些操做異步化,以提升主流程的性能,並與第三方解耦,提升主流程的可用性。
java
在Spring Boot中,或者說在Spring中,咱們實現異步處理通常有如下幾種方式:git
1. 經過 @EnableAsync 與 @Asyc 註解結合實現
2. 經過異步事件實現
3. 經過消息隊列實現github
咱們之前在Spring中提供異步支持通常是在配置文件 applicationContext.xml 中添加相似以下配置spring
<task:annotation-driven executor="executor" /> <task:executor id="executor" pool-size="10-200" queue-capacity="2000"/>
Spring的 @EnableAsync 註解的功能與<task:annotation-driven/>
相似,將其添加於一個 @Configuration 配置類上,可對Spring應用的上下文開啓異步方法支持。 @Async 註解能夠標註在方法或類上,表示某個方法或某個類裏的全部方法須要經過異步方式來調用。 springboot
咱們以一個demo來示例具體用法,demo地址:https://github.com/ronwxy/springboot-demos/tree/master/springboot-async服務器
1. 添加 @EnableAsync 註解微信
在一個 @Configuration 配置類上添加 @EnableAysnc 註解,咱們通常能夠添加到啓動類上,如app
@SpringBootApplication @EnableAsync public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
2. 配置相關的異步執行線程池
框架
@Configuration public class AsyncConfig implements AsyncConfigurer { @Value("${async.corePoolSize:10}") private int corePoolSize; @Value("${async.maxPoolSize:200}") private int maxPoolSize; @Value("${async.queueCapacity:2000}") private int queueCapacity; @Value("${async.keepAlive:5}") private int keepAlive; public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(maxPoolSize); executor.setQueueCapacity(queueCapacity); executor.setKeepAliveSeconds(keepAlive); executor.setThreadNamePrefix("async-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.setDaemon(false); //以用戶線程模式運行 executor.initialize(); return executor; } public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new MyAsyncUncaughtExceptionHandler(); } public static class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { public void handleUncaughtException(Throwable throwable, Method method, Object... objects) { System.out.println("catch exception when invoke " + method.getName()); throwable.printStackTrace(); } } }
可經過配置類的方式對異步線程池進行配置,並提供異步執行時出現異常的處理方法,如
這裏咱們經過實現 AsyncConfigurer 接口提供了一個異步執行線程池對象,各參數的說明能夠參考【線程池的基本原理,看完就懂了】,裏面有很詳細的介紹。且經過實現 AsyncUncaughtExceptionHandler 接口提供了一個異步執行過程當中未捕獲異常的處理類。
3. 定義異步方法
異步方法的定義只須要在類(類上註解表示該類的全部方法都異步執行)或方法上添加 @Async 註解便可,如
@Service public class AsyncService { @Async public void asyncMethod(){ System.out.println("2. running in thread: " + Thread.currentThread().getName()); } @Async public void asyncMethodWithException() { throw new RuntimeException("exception in async method"); } }
4. 測試
咱們能夠經過以下測試類來對異步方法進行測試
@RunWith(SpringRunner.class) @SpringBootTest public class AnnotationBasedAsyncTest { @Autowired private AsyncService asyncService; @Test public void testAsync() throws InterruptedException { System.out.println("1. running in thread: " + Thread.currentThread().getName()); asyncService.asyncMethod(); Thread.sleep(3); } @Test public void testAysncWithException() throws InterruptedException { System.out.println("1. running in thread: " + Thread.currentThread().getName()); asyncService.asyncMethodWithException(); Thread.sleep(3); } }
由於異步方法在一個新的線程中執行,可能在主線程執行完後還沒來得及處理,因此經過sleep來等待它執行完成。具體執行結果讀者可自行嘗試運行,這裏就不貼圖了。
第二種方式是經過Spring框架的事件監聽機制實現,但Spring的事件監聽默認是同步執行的,因此實際上仍是須要藉助 @EnableAsync 與 @Async 來實現異步。
1. 添加 @EnableAsync 註解
與上同,可添加到啓動類上。
2. 自定義事件類
經過繼承 ApplicationEvent 來自定義一個事件
public class MyEvent extends ApplicationEvent { private String arg; public MyEvent(Object source, String arg) { super(source); this.arg = arg; } //getter/setter }
3. 定義事件處理類
支持兩種形式,一是經過實現 ApplicationListener 接口,以下
@Component @Async public class MyEventHandler implements ApplicationListener<MyEvent> { public void onApplicationEvent(MyEvent event) { System.out.println("2. running in thread: " + Thread.currentThread().getName()); System.out.println("2. arg value: " + event.getArg()); } }
二是經過 @EventListener 註解,以下
@Component public class MyEventHandler2 { @EventListener @Async public void handle(MyEvent event){ System.out.println("3. running in thread: " + Thread.currentThread().getName()); System.out.println("3. arg value: " + event.getArg()); } }
注意二者都須要添加 @Async 註解,不然默認是同步方式執行。
4. 定義事件發送類
能夠經過實現 ApplicationEventPublisherAware 接口來使用 ApplicationEventPublisher 的 publishEvent()方法發送事件,
@Component public class MyEventPublisher implements ApplicationEventPublisherAware { protected ApplicationEventPublisher applicationEventPublisher; public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } public void publishEvent(ApplicationEvent event){ this.applicationEventPublisher.publishEvent(event); } }
5. 測試
能夠經過以下測試類來進行測試,
@RunWith(SpringRunner.class) @SpringBootTest public class EventBasedAsyncTest { @Autowired private MyEventPublisher myEventPublisher; @Test public void testAsync() throws InterruptedException { System.out.println("1. running in thread: " + Thread.currentThread().getName()); myEventPublisher.publishEvent(new MyEvent(this,"testing event based async")); Thread.sleep(3); } }
運行後發現兩個事件處理類都執行了,由於二者都監聽了同一個事件 MyEvent 。
以上兩種方式都是基於服務器本機運行,若是服務進程出現異常退出,可能致使異步執行中斷。若是須要保證任務執行的可靠性,能夠藉助消息隊列的持久化與重試機制。阿里雲上的消息隊列服務提供了幾種類型的消息支持,如順序消息、定時/延時消息、事務消息等(詳情可參考:https://help.aliyun.com/document_detail/29532.html?spm=5176.234368.1278132.btn4.6f43db25Rn8oey ),若是項目是基於阿里雲部署的,能夠考慮使用其中一類消息服務來實現業務需求。
本文對spring boot下異步處理的幾種方法進行了介紹,若是對任務執行的可靠性要求不高,則推薦使用第一種方式,若是可靠性要求較高,則推薦使用自建消息隊列或雲消息隊列服務的方式。
本文demo源碼地址:https://github.com/ronwxy/springboot-demos/tree/master/springboot-async/src/main/java/cn/jboost/async
個人我的博客地址:http://blog.jboost.cn
個人微信公衆號:jboost-ksxy (一個不僅有技術乾貨的公衆號,歡迎關注,及時獲取更新內容)
———————————————————————————————————————————————————————————————
原文出處:https://www.cnblogs.com/spec-dog/p/11229633.html