Spring中提供了完整的事件處理機制,本身底層內建實現了一些事件和監聽,同時支援開發者擴充套件自己的事件和監聽實現。
一般這種基於事件的實現在專案實際開發中我們主要用來解耦,和做非同步處理(預設是同步),提供應用的響應速度。
核心架構
先簡要看一下,在Spring中要實現自定義事件監聽需要涉及哪些介面類,這裡忽略非同步的引用、註解的實現,後面會說到。
基本實現步驟
- 自定義事件:一般繼承自ApplicationEvent即可,注意裡面要去定義和實現自己的事件方法,也就是具體這個事件要做什麼事,一般就在事件類、或者基於事件類去實現即可。
- 事件釋出:業務程式碼中注入ApplicationEventPublisher類,然後再具體業務方法中呼叫publishEvent方法,傳入上面自定義的事件,以及自定義的必要引數等資訊
- 實現事件監聽:有了事件、也釋出了,那必須有對應的監聽來呼叫具體的事件,一般實現ApplicationListener泛型傳入自己的事件型別即可
注意事項
- 異常和事物:預設情況下事件的釋出、監聽處理都是和當前業務執行緒繫結到一起的,也就是在同一個執行緒中操作事件任務。因此無論是事件釋出時導致異常,或者是具體事件任務實現的方法異常,都會導致當前業務異常;相應的如果當前業務有事物,那麼異常了也會回滾。
- 事件型別:首先一定要自定義自己的事件,其次在監聽的時候也是監聽自己的事件,而不是監聽基類或者介面然後去判斷,這樣反而失去了基於事件監聽程式設計靈活性,同時也違法開閉原則,並不利於後期擴充套件。具體事件中可以定義其他一些額外的引數,這樣方便在具體方法中傳參使用
- 事件順序:一次可以釋出多個事件,無論是同一個還是不同的,執行順序預設也是按照發布順序。
場景應用
這裡以訂單完成和推送給平臺訂單相關資料為業務模型來舉例說明。Spring4.2之後提供了註解來實現事件監聽,非常的方便,這裡我們使用註解的方式實現監聽即可。
- 縮略的業務類:包含事件的釋出
@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 定義其他引數等
}
}
- 監聽實現:使用註解,注意這裡我使用了 事務監聽註解 ,按照具體業務場景可以選擇具體的註解,比如最常用的@EventListener。因為我這裡的訴求是當前事物提交完成之後再去推送訊息,而且實際情況是啟用了非同步監聽來實現,同時有的人在監聽的方法中可能還執行了回查,也就是去查詢業務中提交的資料,那如果這裡不標記為事物提交之後執行,在非同步情況下無法獲取到資料
@Component
public class TradeStatusEventListener {
@TransactionalEventListener(phase= TransactionPhase.AFTER_COMMIT, fallbackExecution=true)
void handlerAfterComplete(TradeStatusEvent event) {
event.send();
}
}
非同步實現
所謂非同步實現,一般是指非同步監聽,將主體業務邏輯和訊息監聽任務放到不同的執行緒去執行,提高業務的響應速度。
Springboot中我們有多個辦法來實現非同步監聽執行,最簡單、最直接的就和非同步方法實現一模一樣,只需在監聽方法上加上@Async註解(前提是啟用了非同步執行)
- 第一種辦法:Configuration配置類中加上註解@EnableAsync,啟用Spring的非同步方法執行能力。然後在監聽方法上加上@Async註解,標明此方法是非同步執行。Over就這樣就行了【我們沒有配置非同步執行緒對不對?那是會直接new Thread()來執行非同步任務嗎,當然不是,而是Spring預設提供並初始化了一個專門用來執行非同步任務的執行緒池ThreadPoolTaskExecutor,會接管所有的非同步任務在同一個執行緒池中執行。也支援定製化處理,後續我們會說到】
@Configuration
@EnableAsync
public class AppConfig{}
//````
@Component
public class TradeStatusEventListener {
@Async
@TransactionalEventListener(phase= TransactionPhase.AFTER_COMMIT, fallbackExecution=true)
void handlerAfterComplete(TradeStatusEvent event) {
event.send();
}
}
- 第二種辦法:如果說不想全域性開啟非同步,只是想給事件監聽的程式碼實現非同步任務呢?那最簡單就是直接在監聽哪裡new Thread().start(),不受控、不優雅,但是業務場景簡單,訪問量小的情況下也不是不可以。那要規範一點呢,就是自己建立一個執行緒池,比如ExecutorService executorService = Executors.newCachedThreadPool();然後在event.send哪裡使用executorService.execute(..)執行即可。
- 第三種辦法:優雅點實現,建立SimpleApplicationEventMulticaster的Bean,然後建立一個執行緒池給塞進去,注意需要把自定義實現注入到Spring容器中。其他程式碼不用做任何修改,就像同步邏輯一樣,在事件釋出的時候廣播會使用multicastEvent呼叫taskExecutor獲取一個執行緒去執行監聽任務
@Configuration
public class AppConfig{
@Bean
public SimpleApplicationEventMulticaster simpleApplicationEventMulticaster(){
SimpleApplicationEventMulticaster mu = new SimpleApplicationEventMulticaster();
//這裡我使用spring提供的任務構造器建立了一個立即執行的有界佇列任務執行緒池
Executor taskExecutor = new TaskExecutorBuilder().corePoolSize(8).maxPoolSize(200).queueCapacity(20).threadNamePrefix("trade-send-").build();
mu.setTaskExecutor(taskExecutor);
//設定異常處理
mu.setErrorHandler((t)->{
//logger.error("==========呼叫平臺傳送訊息方法失敗,",t);
});
return mu;
}
}
框架原理
- 為什麼非同步監聽只需要@EnableAsync、以及在方法上加上@Async就可以了呢?
-
當我們使用Springboot,引入starter時會自動引入spring-boot-autoconfigure,此包裡面實現了很多自動配置的功能(約定大於配置)名字都是xxxAutoConfiguration,比如我們這裡要說的就是TaskExecutionAutoConfiguration,容器啟動的時候就會載入和建立預設的任務執行緒池,可以透過spring.task.execution開頭屬性來配置。需要注意的是,無論是否加入@EnableAsync註解TaskExecutionAutoConfiguration都會初始化一個預設的執行緒池,因為這個是全域性的。
-
@EnableAsync的作用是在容器啟動的時候,告訴Spring我可要支援非同步處理任務了,你看著辦。Spring所好的朋友,我給你準備了一個專門搞事的攔截器。
-
當我們加入了註解,Spring會將按照配置將準備工作全部做完,從而做到開箱即用,直接一步到位。
-
總結
- Spring事件模型的四個核心:事件源也就是業務方、事件、廣播器、監聽器
- 事件機制支援同步、非同步,按需調整和使用。使用非同步監聽時,推薦使用執行緒池管理執行緒,高效、穩定而且易於維護。
- 使用Springboot時透過註解的方式監聽、啟用非同步盡享絲滑。實際原理核心就是觀察者模式。