Springboot異步事件配置和使用示例詳解
Spring中提供了完整的事件處理機(jī)制,本身底層內(nèi)置實(shí)現(xiàn)了一些事件和監(jiān)聽,同時(shí)支持開發(fā)者擴(kuò)展自己的事件和監(jiān)聽實(shí)現(xiàn)。
一般這種基于事件的實(shí)現(xiàn)在項(xiàng)目實(shí)際開發(fā)中我們主要用來解耦,和做異步處理(默認(rèn)是同步),提供應(yīng)用的響應(yīng)速度。
核心架構(gòu)
先簡要看一下,在Spring中要實(shí)現(xiàn)自定義事件監(jiān)聽需要涉及哪些接口類,這里忽略異步的引用、注解的實(shí)現(xiàn),后面會(huì)說到。
基本實(shí)現(xiàn)步驟
- 自定義事件:一般繼承自ApplicationEvent即可,注意里面要去定義和實(shí)現(xiàn)自己的事件方法,也就是具體這個(gè)事件要做什么事,一般就在事件類、或者基于事件類去實(shí)現(xiàn)即可。
- 事件發(fā)布:業(yè)務(wù)代碼中注入ApplicationEventPublisher類,然后再具體業(yè)務(wù)方法中調(diào)用publishEvent方法,傳入上面自定義的事件,以及自定義的必要參數(shù)等信息
- 實(shí)現(xiàn)事件監(jiān)聽:有了事件、也發(fā)布了,那必須有對(duì)應(yīng)的監(jiān)聽來調(diào)用具體的事件,一般實(shí)現(xiàn)ApplicationListener泛型傳入自己的事件類型即可
注意事項(xiàng)
- 異常和事物:默認(rèn)情況下事件的發(fā)布、監(jiān)聽處理都是和當(dāng)前業(yè)務(wù)線程綁定到一起的,也就是在同一個(gè)線程中操作事件任務(wù)。因此無論是事件發(fā)布時(shí)導(dǎo)致異常,或者是具體事件任務(wù)實(shí)現(xiàn)的方法異常,都會(huì)導(dǎo)致當(dāng)前業(yè)務(wù)異常;相應(yīng)的如果當(dāng)前業(yè)務(wù)有事物,那么異常了也會(huì)回滾。
- 事件類型:首先一定要自定義自己的事件,其次在監(jiān)聽的時(shí)候也是監(jiān)聽自己的事件,而不是監(jiān)聽基類或者接口然后去判斷,這樣反而失去了基于事件監(jiān)聽編程靈活性,同時(shí)也違法開閉原則,并不利于后期擴(kuò)展。具體事件中可以定義其他一些額外的參數(shù),這樣方便在具體方法中傳參使用
- 事件順序:一次可以發(fā)布多個(gè)事件,無論是同一個(gè)還是不同的,執(zhí)行順序默認(rèn)也是按照發(fā)布順序。
場景應(yīng)用
這里以訂單完成和推送給平臺(tái)訂單相關(guān)數(shù)據(jù)為業(yè)務(wù)模型來舉例說明。Spring4.2之后提供了注解來實(shí)現(xiàn)事件監(jiān)聽,非常的方便,這里我們使用注解的方式實(shí)現(xiàn)監(jiān)聽即可。
- 縮略的業(yè)務(wù)類:包含事件的發(fā)布
@Resource private ApplicationEventPublisher publisher; public void completeTrade(TradeOrder trade){ tradeMapper.modifyStatus(trade); publisher.publishEvent(new TradeStatusEvent(this,new TradeStatusEvent.Params(trade,"完成訂單"))); }
- 具體事件的定義:繼承自ApplicationEvent
public class TradeStatusEvent extends ApplicationEvent { private static final Logger logger = LoggerFactory.getLogger(TradeStatusEvent.class); private Params params; public Params getParams(){ return this.params; } public TradeStatusEvent(Object source,Params param) { super(source); this.param = param; } public void send(){ try{ HttpUtils.send("xx.oo", PlatformBean.Builder().note(this.params.note)..build()); } catch(Exception e){ logger.error("TradeStatusEvent處理異常:",e); } } public static class Params { private TradeOrder trade; private String note; //get、set 定義其他參數(shù)等 } }
- 監(jiān)聽實(shí)現(xiàn):使用注解,注意這里我使用了 事務(wù)監(jiān)聽注解 ,按照具體業(yè)務(wù)場景可以選擇具體的注解,比如最常用的@EventListener。因?yàn)槲疫@里的訴求是當(dāng)前事物提交完成之后再去推送消息,而且實(shí)際情況是啟用了異步監(jiān)聽來實(shí)現(xiàn),同時(shí)有的人在監(jiān)聽的方法中可能還執(zhí)行了回查,也就是去查詢業(yè)務(wù)中提交的數(shù)據(jù),那如果這里不標(biāo)記為事物提交之后執(zhí)行,在異步情況下無法獲取到數(shù)據(jù)
@Component public class TradeStatusEventListener { @TransactionalEventListener(phase= TransactionPhase.AFTER_COMMIT, fallbackExecution=true) void handlerAfterComplete(TradeStatusEvent event) { event.send(); } }
異步實(shí)現(xiàn)
所謂異步實(shí)現(xiàn),一般是指異步監(jiān)聽,將主體業(yè)務(wù)邏輯和消息監(jiān)聽任務(wù)放到不同的線程去執(zhí)行,提高業(yè)務(wù)的響應(yīng)速度。
Springboot中我們有多個(gè)辦法來實(shí)現(xiàn)異步監(jiān)聽執(zhí)行,最簡單、最直接的就和異步方法實(shí)現(xiàn)一模一樣,只需在監(jiān)聽方法上加上@Async注解(前提是啟用了異步執(zhí)行)
- 第一種辦法:Configuration配置類中加上注解@EnableAsync,啟用Spring的異步方法執(zhí)行能力。然后在監(jiān)聽方法上加上@Async注解,標(biāo)明此方法是異步執(zhí)行。Over就這樣就行了【我們沒有配置異步線程對(duì)不對(duì)?那是會(huì)直接new Thread()來執(zhí)行異步任務(wù)嗎,當(dāng)然不是,而是Spring默認(rèn)提供并初始化了一個(gè)專門用來執(zhí)行異步任務(wù)的線程池ThreadPoolTaskExecutor,會(huì)接管所有的異步任務(wù)在同一個(gè)線程池中執(zhí)行。也支持定制化處理,后續(xù)我們會(huì)說到】
@Configuration @EnableAsync public class AppConfig{} //```` @Component public class TradeStatusEventListener { @Async @TransactionalEventListener(phase= TransactionPhase.AFTER_COMMIT, fallbackExecution=true) void handlerAfterComplete(TradeStatusEvent event) { event.send(); } }
- 第二種辦法:如果說不想全局開啟異步,只是想給事件監(jiān)聽的代碼實(shí)現(xiàn)異步任務(wù)呢?那最簡單就是直接在監(jiān)聽哪里new Thread().start(),不受控、不優(yōu)雅,但是業(yè)務(wù)場景簡單,訪問量小的情況下也不是不可以。那要規(guī)范一點(diǎn)呢,就是自己創(chuàng)建一個(gè)線程池,比如ExecutorService executorService = Executors.newCachedThreadPool();然后在event.send哪里使用executorService.execute(..)執(zhí)行即可。
- 第三種辦法:優(yōu)雅點(diǎn)實(shí)現(xiàn),創(chuàng)建SimpleApplicationEventMulticaster的Bean,然后創(chuàng)建一個(gè)線程池給塞進(jìn)去,注意需要把自定義實(shí)現(xiàn)注入到Spring容器中。其他代碼不用做任何修改,就像同步邏輯一樣,在事件發(fā)布的時(shí)候廣播會(huì)使用multicastEvent調(diào)用taskExecutor獲取一個(gè)線程去執(zhí)行監(jiān)聽任務(wù)
@Configuration public class AppConfig{ @Bean public SimpleApplicationEventMulticaster simpleApplicationEventMulticaster(){ SimpleApplicationEventMulticaster mu = new SimpleApplicationEventMulticaster(); //這里我使用spring提供的任務(wù)構(gòu)造器創(chuàng)建了一個(gè)立即執(zhí)行的有界隊(duì)列任務(wù)線程池 Executor taskExecutor = new TaskExecutorBuilder().corePoolSize(8).maxPoolSize(200).queueCapacity(20).threadNamePrefix("trade-send-").build(); mu.setTaskExecutor(taskExecutor); //設(shè)置異常處理 mu.setErrorHandler((t)->{ //logger.error("==========調(diào)用平臺(tái)發(fā)送消息方法失敗,",t); }); return mu; } }
框架原理
- 為什么異步監(jiān)聽只需要@EnableAsync、以及在方法上加上@Async就可以了呢?
當(dāng)我們使用Springboot,引入starter時(shí)會(huì)自動(dòng)引入spring-boot-autoconfigure,此包里面實(shí)現(xiàn)了很多自動(dòng)配置的功能(約定大于配置)名字都是xxxAutoConfiguration,比如我們這里要說的就是TaskExecutionAutoConfiguration,容器啟動(dòng)的時(shí)候就會(huì)加載和創(chuàng)建默認(rèn)的任務(wù)線程池,可以通過spring.task.execution開頭屬性來配置。需要注意的是,無論是否加入@EnableAsync注解TaskExecutionAutoConfiguration都會(huì)初始化一個(gè)默認(rèn)的線程池,因?yàn)檫@個(gè)是全局的。
@EnableAsync的作用是在容器啟動(dòng)的時(shí)候,告訴Spring我可要支持異步處理任務(wù)了,你看著辦。Spring所好的朋友,我給你準(zhǔn)備了一個(gè)專門搞事的攔截器。
當(dāng)我們加入了注解,Spring會(huì)將按照配置將準(zhǔn)備工作全部做完,從而做到開箱即用,直接一步到位。
總結(jié)
- Spring事件模型的四個(gè)核心:事件源也就是業(yè)務(wù)方、事件、廣播器、監(jiān)聽器
- 事件機(jī)制支持同步、異步,按需調(diào)整和使用。使用異步監(jiān)聽時(shí),推薦使用線程池管理線程,高效、穩(wěn)定而且易于維護(hù)。
- 使用Springboot時(shí)通過注解的方式監(jiān)聽、啟用異步盡享絲滑。實(shí)際原理核心就是觀察者模式。
到此這篇關(guān)于Springboot異步事件配置和使用的文章就介紹到這了,更多相關(guān)Springboot異步事件配置內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Skywalking改成適配阿里云等帶Http?Basic的Elasticsearch服務(wù)
這篇文章主要介紹了改造Skywalking支持阿里云等帶Http?Basic的Elasticsearch服務(wù)2022-02-02關(guān)于Java?CPU或內(nèi)存使用率過高問題定位
Spring?cloud微服務(wù)廣泛應(yīng)用后,服務(wù)的監(jiān)控和運(yùn)維壓力也與日俱增,經(jīng)常有服務(wù)出現(xiàn)CPU或者內(nèi)存使用率過高的告警,那么遇到這樣的問題我們該如何排查呢?我們可以借助哪些工具來定位問題呢?本文將介紹一下遇到此類問題的解決思路和方法2024-10-10Java開發(fā)中常用的 Websocket 技術(shù)參考
WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù),當(dāng)然也支持客戶端發(fā)送數(shù)據(jù)到服務(wù)端。2020-09-09使用PageHelper插件實(shí)現(xiàn)Service層分頁
這篇文章主要為大家詳細(xì)介紹了使用PageHelper插件實(shí)現(xiàn)Service層分頁,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04java秒殺系統(tǒng)常見問題庫存超賣解決實(shí)例分析
這篇文章主要為大家介紹了java秒殺系統(tǒng)常見問題庫存超賣解決實(shí)例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11CountDownLatch源碼解析之countDown()
這篇文章主要為大家詳細(xì)解析了CountDownLatch源碼之countDown方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04