Springboot異步事件配置和使用示例詳解
Spring中提供了完整的事件處理機(jī)制,本身底層內(nèi)置實(shí)現(xiàn)了一些事件和監(jiān)聽(tīng),同時(shí)支持開(kāi)發(fā)者擴(kuò)展自己的事件和監(jiān)聽(tīng)實(shí)現(xiàn)。
一般這種基于事件的實(shí)現(xiàn)在項(xiàng)目實(shí)際開(kāi)發(fā)中我們主要用來(lái)解耦,和做異步處理(默認(rèn)是同步),提供應(yīng)用的響應(yīng)速度。
核心架構(gòu)
先簡(jiǎn)要看一下,在Spring中要實(shí)現(xiàn)自定義事件監(jiān)聽(tīng)需要涉及哪些接口類(lèi),這里忽略異步的引用、注解的實(shí)現(xiàn),后面會(huì)說(shuō)到。

基本實(shí)現(xiàn)步驟
- 自定義事件:一般繼承自ApplicationEvent即可,注意里面要去定義和實(shí)現(xiàn)自己的事件方法,也就是具體這個(gè)事件要做什么事,一般就在事件類(lèi)、或者基于事件類(lèi)去實(shí)現(xiàn)即可。
- 事件發(fā)布:業(yè)務(wù)代碼中注入ApplicationEventPublisher類(lèi),然后再具體業(yè)務(wù)方法中調(diào)用publishEvent方法,傳入上面自定義的事件,以及自定義的必要參數(shù)等信息
- 實(shí)現(xiàn)事件監(jiān)聽(tīng):有了事件、也發(fā)布了,那必須有對(duì)應(yīng)的監(jiān)聽(tīng)來(lái)調(diào)用具體的事件,一般實(shí)現(xiàn)ApplicationListener泛型傳入自己的事件類(lèi)型即可
注意事項(xiàng)
- 異常和事物:默認(rèn)情況下事件的發(fā)布、監(jiān)聽(tīng)處理都是和當(dāng)前業(yè)務(wù)線程綁定到一起的,也就是在同一個(gè)線程中操作事件任務(wù)。因此無(wú)論是事件發(fā)布時(shí)導(dǎo)致異常,或者是具體事件任務(wù)實(shí)現(xiàn)的方法異常,都會(huì)導(dǎo)致當(dāng)前業(yè)務(wù)異常;相應(yīng)的如果當(dāng)前業(yè)務(wù)有事物,那么異常了也會(huì)回滾。
- 事件類(lèi)型:首先一定要自定義自己的事件,其次在監(jiān)聽(tīng)的時(shí)候也是監(jiān)聽(tīng)自己的事件,而不是監(jiān)聽(tīng)基類(lèi)或者接口然后去判斷,這樣反而失去了基于事件監(jiān)聽(tīng)編程靈活性,同時(shí)也違法開(kāi)閉原則,并不利于后期擴(kuò)展。具體事件中可以定義其他一些額外的參數(shù),這樣方便在具體方法中傳參使用
- 事件順序:一次可以發(fā)布多個(gè)事件,無(wú)論是同一個(gè)還是不同的,執(zhí)行順序默認(rèn)也是按照發(fā)布順序。
場(chǎng)景應(yīng)用
這里以訂單完成和推送給平臺(tái)訂單相關(guān)數(shù)據(jù)為業(yè)務(wù)模型來(lái)舉例說(shuō)明。Spring4.2之后提供了注解來(lái)實(shí)現(xiàn)事件監(jiān)聽(tīng),非常的方便,這里我們使用注解的方式實(shí)現(xiàn)監(jiān)聽(tīng)即可。
- 縮略的業(yè)務(wù)類(lèi):包含事件的發(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)聽(tīng)實(shí)現(xiàn):使用注解,注意這里我使用了 事務(wù)監(jiān)聽(tīng)注解 ,按照具體業(yè)務(wù)場(chǎng)景可以選擇具體的注解,比如最常用的@EventListener。因?yàn)槲疫@里的訴求是當(dāng)前事物提交完成之后再去推送消息,而且實(shí)際情況是啟用了異步監(jiān)聽(tīng)來(lái)實(shí)現(xiàn),同時(shí)有的人在監(jiān)聽(tīng)的方法中可能還執(zhí)行了回查,也就是去查詢業(yè)務(wù)中提交的數(shù)據(jù),那如果這里不標(biāo)記為事物提交之后執(zhí)行,在異步情況下無(wú)法獲取到數(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)聽(tīng),將主體業(yè)務(wù)邏輯和消息監(jiān)聽(tīng)任務(wù)放到不同的線程去執(zhí)行,提高業(yè)務(wù)的響應(yīng)速度。
Springboot中我們有多個(gè)辦法來(lái)實(shí)現(xiàn)異步監(jiān)聽(tīng)執(zhí)行,最簡(jiǎn)單、最直接的就和異步方法實(shí)現(xiàn)一模一樣,只需在監(jiān)聽(tīng)方法上加上@Async注解(前提是啟用了異步執(zhí)行)
- 第一種辦法:Configuration配置類(lèi)中加上注解@EnableAsync,啟用Spring的異步方法執(zhí)行能力。然后在監(jiān)聽(tīng)方法上加上@Async注解,標(biāo)明此方法是異步執(zhí)行。Over就這樣就行了【我們沒(méi)有配置異步線程對(duì)不對(duì)?那是會(huì)直接new Thread()來(lái)執(zhí)行異步任務(wù)嗎,當(dāng)然不是,而是Spring默認(rèn)提供并初始化了一個(gè)專(zhuān)門(mén)用來(lái)執(zhí)行異步任務(wù)的線程池ThreadPoolTaskExecutor,會(huì)接管所有的異步任務(wù)在同一個(gè)線程池中執(zhí)行。也支持定制化處理,后續(xù)我們會(huì)說(shuō)到】
@Configuration
@EnableAsync
public class AppConfig{}
//````
@Component
public class TradeStatusEventListener {
@Async
@TransactionalEventListener(phase= TransactionPhase.AFTER_COMMIT, fallbackExecution=true)
void handlerAfterComplete(TradeStatusEvent event) {
event.send();
}
}- 第二種辦法:如果說(shuō)不想全局開(kāi)啟異步,只是想給事件監(jiān)聽(tīng)的代碼實(shí)現(xiàn)異步任務(wù)呢?那最簡(jiǎn)單就是直接在監(jiān)聽(tīng)哪里new Thread().start(),不受控、不優(yōu)雅,但是業(yè)務(wù)場(chǎng)景簡(jiǎn)單,訪問(wèn)量小的情況下也不是不可以。那要規(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)聽(tīng)任務(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)聽(tīng)只需要@EnableAsync、以及在方法上加上@Async就可以了呢?
當(dāng)我們使用Springboot,引入starter時(shí)會(huì)自動(dòng)引入spring-boot-autoconfigure,此包里面實(shí)現(xiàn)了很多自動(dòng)配置的功能(約定大于配置)名字都是xxxAutoConfiguration,比如我們這里要說(shuō)的就是TaskExecutionAutoConfiguration,容器啟動(dòng)的時(shí)候就會(huì)加載和創(chuàng)建默認(rèn)的任務(wù)線程池,可以通過(guò)spring.task.execution開(kāi)頭屬性來(lái)配置。需要注意的是,無(wú)論是否加入@EnableAsync注解TaskExecutionAutoConfiguration都會(huì)初始化一個(gè)默認(rèn)的線程池,因?yàn)檫@個(gè)是全局的。

@EnableAsync的作用是在容器啟動(dòng)的時(shí)候,告訴Spring我可要支持異步處理任務(wù)了,你看著辦。Spring所好的朋友,我給你準(zhǔn)備了一個(gè)專(zhuān)門(mén)搞事的攔截器。

當(dāng)我們加入了注解,Spring會(huì)將按照配置將準(zhǔn)備工作全部做完,從而做到開(kāi)箱即用,直接一步到位。
總結(jié)
- Spring事件模型的四個(gè)核心:事件源也就是業(yè)務(wù)方、事件、廣播器、監(jiān)聽(tīng)器
- 事件機(jī)制支持同步、異步,按需調(diào)整和使用。使用異步監(jiān)聽(tīng)時(shí),推薦使用線程池管理線程,高效、穩(wěn)定而且易于維護(hù)。
- 使用Springboot時(shí)通過(guò)注解的方式監(jiān)聽(tīng)、啟用異步盡享絲滑。實(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)存使用率過(guò)高問(wèn)題定位
Spring?cloud微服務(wù)廣泛應(yīng)用后,服務(wù)的監(jiān)控和運(yùn)維壓力也與日俱增,經(jīng)常有服務(wù)出現(xiàn)CPU或者內(nèi)存使用率過(guò)高的告警,那么遇到這樣的問(wèn)題我們?cè)撊绾闻挪槟兀课覀兛梢越柚男┕ぞ邅?lái)定位問(wèn)題呢?本文將介紹一下遇到此類(lèi)問(wèn)題的解決思路和方法2024-10-10
Java開(kāi)發(fā)中常用的 Websocket 技術(shù)參考
WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù),當(dāng)然也支持客戶端發(fā)送數(shù)據(jù)到服務(wù)端。2020-09-09
使用PageHelper插件實(shí)現(xiàn)Service層分頁(yè)
這篇文章主要為大家詳細(xì)介紹了使用PageHelper插件實(shí)現(xiàn)Service層分頁(yè),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
java秒殺系統(tǒng)常見(jiàn)問(wèn)題庫(kù)存超賣(mài)解決實(shí)例分析
這篇文章主要為大家介紹了java秒殺系統(tǒng)常見(jiàn)問(wèn)題庫(kù)存超賣(mài)解決實(shí)例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
CountDownLatch源碼解析之countDown()
這篇文章主要為大家詳細(xì)解析了CountDownLatch源碼之countDown方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04

