今天去官網查看
spring boot
資料時,在特性中看見了系統的事件及監聽
章節。想一想,spring
的事件應該是在3.x
版本就發佈的功能了,並愈來愈完善,其爲bean
和bean
之間的消息通訊提供了支持。好比,咱們能夠在用戶註冊成功後,發送一份註冊成功的郵件至用戶郵箱或者發送短信。使用事件其實最大做用,應該仍是爲了業務解耦,畢竟用戶註冊成功後,註冊服務的事情就作完了,只須要發佈一個用戶註冊成功的事件,讓其餘監聽了此事件的業務系統去作剩下的事件就行了。對於事件發佈者而言,不須要關心誰監聽了該事件,以此來解耦業務。今天,咱們就來說講spring boot
中事件的使用和發佈。固然了,也可使用像guava
的eventbus
或者異步框架Reactor
來處理此類業務需求的。本文僅僅談論ApplicationEvent
以及Listener
的使用。html
示例前,咱們來了解下相關知識點。java
java中的事件機制通常包括3個部分:EventObject
,EventListener
和Source
。react
EventObjectgit
java.util.EventObject是事件狀態對象的基類,它封裝了事件源對象以及和事件相關的信息。全部java的事件類都須要繼承該類。github
EventListenerweb
java.util.EventListener是一個標記接口,就是說該接口內是沒有任何方法的。全部事件監聽器都須要實現該接口。事件監聽器註冊在事件源上,當事件源的屬性或狀態改變的時候,調用相應監聽器內的回調方法。spring
Source數據庫
事件源不須要實現或繼承任何接口或類,它是事件最初發生的地方。由於事件源須要註冊事件監聽器,因此事件源內須要有相應的盛放事件監聽器的容器。設計模式
java
的事件機制是一個觀察者模式。你們能夠根據這個模式,本身實現一個。能夠看看這篇博文:《java事件機制》一個很簡單的實例。springboot
ApplicationEvent
以及Listener
是Spring
爲咱們提供的一個事件監聽、訂閱的實現,內部實現原理是觀察者設計模式,設計初衷也是爲了系統業務邏輯之間的解耦,提升可擴展性以及可維護性。
ApplicationEvent
就是Spring
的事件接口ApplicationListener
就是Spring
的事件監聽器接口,全部的監聽器都實現該接口ApplicationEventPublisher
是Spring
的事件發佈接口,ApplicationContext
實現了該接口ApplicationEventMulticaster
就是Spring
事件機制中的事件廣播器,默認實現SimpleApplicationEventMulticaster
在Spring
中一般是ApplicationContext
自己擔任監聽器註冊表的角色,在其子類AbstractApplicationContext
中就聚合了事件廣播器ApplicationEventMulticaster
和事件監聽器ApplicationListnener
,而且提供註冊監聽器的addApplicationListnener
方法。
其執行的流程大體爲:
當一個事件源產生事件時,它經過事件發佈器
ApplicationEventPublisher
發佈事件,而後事件廣播器ApplicationEventMulticaster
會去事件註冊表ApplicationContext
中找到事件監聽器ApplicationListnener
,而且逐個執行監聽器的onApplicationEvent
方法,從而完成事件監聽器的邏輯。
在Spring
中,使用註冊監聽接口,除了繼承ApplicationListener
接口外,還可使用註解@EventListener
來監聽一個事件,同時該註解還支持SpEL
表達式,來觸發監聽的條件,好比只接受編碼爲001
的事件,從而實現一些個性化操做。下文示例中會簡單舉例下。
簡單來講,在Java中,經過java.util. EventObject來描述事件,經過java.util. EventListener來描述事件監聽器,在衆多的框架和組件中,創建一套事件機制一般是基於這兩個接口來進行擴展。
在
SpringBoot
的1.5.x
中,提供了幾種事件,供咱們在開發過程當中進行更加便捷的擴展及差別化操做。
ApplicationStartingEvent
:springboot啓動開始的時候執行的事件
ApplicationEnvironmentPreparedEvent
:spring boot
對應Enviroment已經準備完畢,但此時上下文context
尚未建立。在該監聽中獲取到ConfigurableEnvironment
後能夠對配置信息作操做,例如:修改默認的配置信息,增長額外的配置信息等等。
ApplicationPreparedEvent
:spring boot
上下文context
建立完成,但此時spring
中的bean
是沒有徹底加載完成的。在獲取完上下文後,能夠將上下文傳遞出去作一些額外的操做。值得注意的是:在該監聽器中是沒法獲取自定義bean並進行操做的。
ApplicationReadyEvent
:springboot
加載完成時候執行的事件。
ApplicationFailedEvent
:spring boot
啓動異常時執行事件。
從官網文檔中,咱們能夠知道,因爲一些事件實在上下文爲加載完觸發的,因此沒法使用註冊bean
的方式來聲明,文檔中能夠看出,能夠經過SpringApplication.addListeners(…)
或者SpringApplicationBuilder.listeners(…)
來添加,或者添加META-INF/spring.factories
文件z中添加監聽類也是能夠的,這樣會自動加載。
org.springframework.context.ApplicationListener=com.example.project.MyListener
啓動類中添加:
@SpringBootApplication public class Application { public static void main(String[] args){ SpringApplication app =new SpringApplication(Application.class); app.addListeners(new MyApplicationStartingEventListener());//加入自定義的監聽類 app.run(args); } }
因此在須要的時候,能夠經過適當的監聽以上事件,來完成一些業務操做。
經過以上的介紹,咱們來定義一個自定義事件的發佈和監聽。
0.加入POM依賴,這裏爲了演示加入了web
依賴。事件相關類都在spring-context
包下。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
1.自定義事件源和實體。
MessageEntity.java
/** * 消息實體類 * @author oKong * */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class MessageEntity { String message; String code; }
CustomEvent.java
/** * 編寫事件源 * @author oKong * */ @SuppressWarnings("serial") public class CustomEvent extends ApplicationEvent{ private MessageEntity messageEntity; public CustomEvent(Object source, MessageEntity messageEntity) { super(source); this.messageEntity = messageEntity; } public MessageEntity getMessageEntity() { return this.messageEntity; } }
2.編寫監聽類
使用@EventListener
方式。
/** * 監聽配置類 * * @author oKong * */ @Configuration @Slf4j public class EventListenerConfig { @EventListener public void handleEvent(Object event) { //監聽全部事件 能夠看看 系統各種時間 發佈了哪些事件 //可根據 instanceof 監聽想要監聽的事件 // if(event instanceof CustomEvent) { // // } log.info("事件:{}", event); } @EventListener public void handleCustomEvent(CustomEvent customEvent) { //監聽 CustomEvent事件 log.info("監聽到CustomEvent事件,消息爲:{}, 發佈時間:{}", customEvent.getMessageEntity(), customEvent.getTimestamp()); } /** * 監聽 code爲oKong的事件 */ @EventListener(condition="#customEvent.messageEntity.code == 'oKong'") public void handleCustomEventByCondition(CustomEvent customEvent) { //監聽 CustomEvent事件 log.info("監聽到code爲'oKong'的CustomEvent事件,消息爲:{}, 發佈時間:{}", customEvent.getMessageEntity(), customEvent.getTimestamp()); } @EventListener public void handleObjectEvent(MessageEntity messageEntity) { //這個和eventbus post方法同樣了 log.info("監聽到對象事件,消息爲:{}", messageEntity); } }
** 注意:Spring
中,事件源不強迫繼承ApplicationEvent
接口的,也就是能夠直接發佈任意一個對象類。但內部實際上是使用PayloadApplicationEvent
類進行包裝了一層。這點和guava
的eventBus
相似。**
並且,使用@EventListener
的condition
能夠實現更加精細的事件監聽,condition
支持SpEL
表達式,可根據事件源的參數來判斷是否監聽。
使用ApplicationListener
方式。
@Component @Slf4j public class EventListener implements ApplicationListener<CustomEvent>{ @Override public void onApplicationEvent(CustomEvent event) { //這裏也能夠監聽全部事件 使用 ApplicationEvent 類便可 //這裏僅僅監聽自定義事件 CustomEvent log.info("ApplicationListener方式監聽事件:{}", event); } }
3.編寫控制類,示例發佈事件。
/** * 模擬觸發事件 * @author oKong * */ @RestController @RequestMapping("/push") @Slf4j public class DemoController { /** * 注入 事件發佈類 */ @Autowired ApplicationEventPublisher eventPublisher; @GetMapping public String push(String code,String message) { log.info("發佈applicationEvent事件:{},{}", code, message); eventPublisher.publishEvent(new CustomEvent(this, MessageEntity.builder().code(code).message(message).build())); return "事件發佈成功!"; } @GetMapping("/obj") public String pushObject(String code,String message) { log.info("發佈對象事件:{},{}", code, message); eventPublisher.publishEvent(MessageEntity.builder().code(code).message(message).build()); return "對象事件發佈成功!"; } }
4.編寫啓動類。
/** * 事件監聽 * * @author oKong * */ @SpringBootApplication @Slf4j public class EventAndListenerApplication { public static void main(String[] args) throws Exception { SpringApplication app =new SpringApplication(EventAndListenerApplication.class); app.addListeners(new MyApplicationStartingEventListener());//加入自定義的監聽類 app.run(args); log.info("spring-boot-event-listener-chapter32啓動!"); } }
這裏,建立了個ApplicationStartingEvent
事件監聽類。
/** * 示例-啓動事件 * @author oKong * */ public class MyApplicationStartingEventListener implements ApplicationListener<ApplicationStartingEvent>{ @Override public void onApplicationEvent(ApplicationStartingEvent event) { // TODO Auto-generated method stub //因爲 log相關還未加載 使用了也輸出不了的 // log.info("ApplicationStartingEvent事件發佈:{}", event); System.out.println("ApplicationStartingEvent事件發佈:" + event.getTimestamp()); } }
5.啓動應用,控制檯能夠看出,在啓動時,咱們監聽到了ApplicationStartingEvent
事件
首先訪問下:http://127.0.0.1:8080/push?code=lqdev&message=趔趄的猿
,能夠看見事件已經被監聽到了,而監聽了code
爲oKong
的監聽未觸發。
而後訪問下:http://127.0.0.1:8080/push?code=oKong&message=趔趄的猿
,能夠看見此時三個監聽事件都接收到了事件了。
此時,因爲寫了一個監聽全部事件的方法,能夠看見請求結束後,會發佈一個事件ServletRequestHandledEvent
,裏面記錄了請求的時間、請求url、請求方式等等信息。
事件:ServletRequestHandledEvent: url=[/push]; client=[127.0.0.1]; method=[GET]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[2ms]; status=[OK]
默認狀況下,監聽事件都是同步執行的。在須要異步處理時,能夠在方法上加上@Async
進行異步化操做。此時,能夠定義一個線程池,同時開啓異步功能,加入@EnableAsync
。
對於異步處理,能夠查看以前發佈的文章:《第二十一章:異步開發之異步調用》。裏面有詳細的介紹異步調用,這裏就不闡述了。
異步簡單示例:
/** * 監聽 code爲oKong的事件 */ @Async @EventListener(condition="#customEvent.messageEntity.code == 'oKong'") public void handleCustomEventByCondition(CustomEvent customEvent) { //監聽 CustomEvent事件 log.info("監聽到code爲'oKong'的CustomEvent事件,消息爲:{}, 發佈時間:{}", customEvent.getMessageEntity(), customEvent.getTimestamp()); }
當一些場景下,好比在用戶註冊成功後,即數據庫事務提交了,以後再異步發送郵件等,否則會發生數據庫插入失敗,但事件卻發佈了,也就是郵件發送成功了的狀況。此時,咱們可使用@TransactionalEventListener
註解或者TransactionSynchronizationManager
類來解決此類問題,也就是:事務成功提交後,再發布事件。固然也能夠利用返回上層(事務提交後)再發布事件的方式了,只是不夠優雅而已罷了,其實能起做用就行了,是吧~
本例中未使用到數據庫,就不示例了,都在Spring-tx
包下。
具體可查看文章:Spring Event 事件中的事務控制
本章節主要簡單介紹了
spring
的事件機制。感興趣的同窗,能夠編寫一個監聽全部事件的方法,而後看看系統運行各種請求或者相關操做時,系統會發布哪些事件,瞭解後能夠在以後遇見一些特殊業務需求時,能夠適當的監聽相關的事件來完成特定的業務公共。同時對這種觀察者模式,你們還能夠看看eventbus
和reactor
了。後者沒用過,有時間卻是能夠看看。最近買了本RxJava2
書籍,確實要好好補課下了。
目前互聯網上不少大佬都有
SpringBoot
系列教程,若有雷同,請多多包涵了。原創不易,碼字不易,還但願你們多多支持。若文中有所錯誤之處,還望提出,謝謝。
499452441
lqdevOps
我的博客:http://blog.lqdev.cn
完整示例:https://github.com/xie19900123/spring-boot-learning/tree/master/chapter-32
原文地址:https://blog.lqdev.cn/2018/11/06/springboot/chapter-thirty-two/