本次咱們來學習 Spring 的事件處理,源於實際工做中遇到的項目需求:在一個支付的下單場景中,當用戶真正支付成功,服務器收到回調後就須要及時更新訂單數據狀態來保證數據一致。一般作法就是在回調方法裏直接使用訂單服務更新數據, 然而這樣實現上兩個模塊出現了緊密耦合,若是訂單更新的操做須要進行調整,那麼在支付回調的代碼塊中也須要被修改。html
爲了不這樣狀況發生,我採用了 Spring 事件發佈與訂閱的方式來實現接受支付回調,發佈通知更新訂單狀態的這個功能,讓訂單服務更新數據的操做只依賴特定的事件,而不用關心具體的觸發對象,也能達到代碼複用的目的。java
本文主要內容涉及以下:git
示例項目:github
- spring-events:github.com/wrcj12138aa…
環境支持:web
- JDK 8
- SpringBoot 2.1.4
- Maven 3.6.0
Spring 程序啓動過程當中會有不一樣的事件通知,內置標準的事件有 5 種:spring
事件 | 說明 |
---|---|
ContextRefreshedEvent |
當 Spring 容器處於初始化或者刷新階段時就會觸發,事實是ApplicationContext#refresh() 方法被調用時,此時容器已經初始化完畢。 |
ContextStartedEvent |
當調用 ConfigurableApplicationContext 接口下的 start() 方法時觸發,表示 Spring 容器啓動;一般用於 Spring 容器顯式關閉後的啓動。 |
ContextStoppedEvent |
當調用 ConfigurableApplicationContext 接口下的 stop() 方法時觸發,表示 Spring 容器中止,此時能經過其 start() 方法重啓容器。 |
ContextClosedEvent |
當 Spring 容器調用 ApplicationContext#close() 方法時觸發,此時 Spring 的 beans 都已經被銷燬,而且不會從新啓動和刷新。 |
RequestHandledEvent |
只在 Web 應用下存在,當接受到 HTTP 請求並處理後就會觸發,實際傳遞的默認實現類 ServletRequestHandledEvent |
一般狀況下,Spring 程序都會接收到 ContextRefreshedEvent
, ContextClosedEvent
事件的通知。數據庫
知道了 Spring 自帶的事件有哪些後,咱們就能夠針對一些場景利用事件機制來實現需求,好比說在 Spring 啓動後初始化資源,加載緩存數據到內存中等等。代碼實現也很簡單,以下:express
@Component
public class InitalizeListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
System.out.println("Spring 容器啓動 獲取到 Application Context 對象 " + applicationContext);
//TODO 初始化資源,加載緩存數據到內存
}
}
// 啓動 Spring 程序後,控制檯出現以下日誌:
// Spring 容器啓動 獲取到 Application Context 對象 org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6950ed69, started on Sun May 26 12:19:33 CST 2019
複製代碼
咱們能夠從 ContextRefreshedEvent
事件中獲取到 ApplicationContext
對象,從而獲取 Spring 容器中任何已經裝載的 Bean 進行自定義的操做。api
從 Spring 4.2 開始,Spring 又提供了更靈活的,註解驅動的事件偵聽處理方式。主要使用 @EventListener
註解來標記須要監聽程序事件的方法,底層由 EventListenerMethodProcessor
對象將標註的方法轉爲成 ApplicationListener
實例。數組
爲何說這個註解方式偵聽事件更加靈活呢,咱們能夠先看下 @EventListener
註解的源碼。
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EventListener {
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String condition() default "";
}
複製代碼
EventListener
註解主要有兩個屬性:classes
和 condition
。 classes
表示所須要偵聽的事件類型,是個數組,因此容許在單個方法裏進行多個不一樣事件的偵聽,以此作到複用的效果;condition
顧名思義就是用來定義所偵聽事件是否處理的前置條件,這裏須要注意的是使用 Spring Expression Language (SpEL)定義條件,好比 #root.event
表示了具體的 ApplicationEvent
對象, 使用方式能夠參考下方示例代碼:
@Component
public class AnnotationListener {
@EventListener(value = {ContextRefreshedEvent.class, ContextStartedEvent.class, ContextStoppedEvent.class, ContextClosedEvent.class, RequestHandledEvent.class}, condition = "#root.event != null")
public void listener(ApplicationEvent event) {
System.out.println(Thread.currentThread() + " 接收到 Spring 事件:" + event);
}
}
複製代碼
這裏須要注意的是,註解 @EventListener
標記的方法參數類型再也不限制必須是 ApplicationEvent
的子類,沒有實現 ApplicationListener
接口方法的約束,也讓事件變得更加靈活。
另外,使用 @EventListener
還支持事件的傳遞,將當前事件處理好的結果封裝後發佈一個新的事件,實現的方式就是讓偵聽方法返回非 null
值時,就視爲事件繼續傳播,以下面的示例代碼:
@Component
@Order(2)
public class CustomEventListener {
@EventListener
public SecondCustomEvent listener(CustomEvent event) {
System.out.println(Thread.currentThread() + "CustomEventListener接受到自定義事件:" + event);
return new SecondCustomEvent(this, event.toString());
}
}
複製代碼
當咱們對單個事件存在多個偵聽器時,可能會因爲需求想要指定偵聽器的執行順序,這一點 Spring 也爲咱們考慮到了,只要使用 @Order
註解聲明監聽類或者監聽方法便可,根據 @Order
的 value
大小來肯定執行順序,越小越優先執行。
@EventListener
@Order(42)
public void processEvent(Event event) {
}
複製代碼
在瞭解如何偵聽 Spring 事件後,咱們再來看下如何實現自定義的事件發佈和偵聽處理。首先就要介紹 Spring 中事件機制的三類對象:
Event
:所須要觸發的具體事件對象,一般擴展 ApplicationEvent
實現。Publisher
:觸發事件發佈的對象,Spring 提供了 ApplicationEventPublisher
對象供咱們使用,使用它的publishEvent()
方法就能夠發佈該事件。Listener
:偵聽事件發生的對象,也就是接受回調進行處理的地方,能夠經過 實現 ApplicationListener
接口,或者使用前面提到的 @EventListener
註解聲明爲事件的偵聽器。接下來就簡單看下,一個自定義事件從聲明到發佈訂閱的代碼示例。
public class CustomEvent extends ApplicationEvent {
private String data;
public CustomEvent(Object source, String data) {
super(source);
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public String toString() {
return "CustomEvent{" +
"data='" + data + '\'' +
", source=" + source +
'}';
}
}
複製代碼
@Component
public class CustomeEventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void publishEvent(String message) {
System.out.println("開始發佈事件 " + message);
applicationEventPublisher.publishEvent(new CustomEvent(this, message));
}
}
複製代碼
建立事件發佈者有兩種方式,一種是使用 @Autowire
註解,經過 Spring 容器的依賴注入功能,直接注入 ApplicationEventPublisher
對象,或者實現 ApplicationEventPublisherAware
接口,在 Spring 容器啓動時由 Spring 設置。
@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {
@Override
public void onApplicationEvent(CustomEvent event) {
System.out.println(Thread.currentThread()+"CustomEventListener接受到自定義事件:" + event);
}
}
複製代碼
定義事件偵聽器時,咱們經過實現 ApplicationListener
接口,指定了事件類型,這樣在處理事件時就不避免了事件類型判斷和轉換。
關於事件偵聽器還須要注意的一點是:Spring 事件處理默認是同步的,這一點在 Spring 官方文檔全部說起,咱們先解讀下官方描述:
You can register as many event listeners as you wish, but note that, by default, event listeners receive events synchronously. This means that the
publishEvent()
method blocks until all listeners have finished processing the event. One advantage of this synchronous and single-threaded approach is that, when a listener receives an event, it operates inside the transaction context of the publisher if a transaction context is available. If another strategy for event publication becomes necessary, See the javadoc for Spring’sApplicationEventMulticaster
interface.
當發佈者執行了 publishEvent()
方法,默認狀況下方法所在的當前線程就會阻塞,直到全部該事件相關的偵聽器將事件處理完成。而這樣採用單線程同步方式處理的好處主要是能夠保證讓事件處理與發佈者處於同一個事務環境裏,若是多個偵聽方法涉及到數據庫操做時保證了事務的存在。
固然 Spring 也提供了異步偵聽事件的方式,這裏主要依賴 ApplicationEventMulticaster
接口,能夠理解爲廣播方式,爲了便於使用,Spring 提供一個簡易的實現類 SimpleApplicationEventMulticaster
供咱們直接使用,只須要將這個對象註冊到 Spring 容器便可。
@Configuration
public class AsynchronousSpringEventsConfig {
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster
= new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
}
複製代碼
這裏 ApplicationEventMulticaster
Bean 須要一個 java.util.concurrent.Executor
對象做爲事件處理的線程池,咱們直接使用 Spring 提供的 SimpleAsyncTaskExecutor
對象,每次事件處理都會有建立新的線程。
注意:註冊
ApplicationEventMulticaster
Bean 後全部的事件偵聽處理都會變成的異步形式,若是須要針對特定的事件偵聽採用異步方式的話:可使用@EventListener
和@Async
組合來實現。(前提是 Spring 程序啓用@EnableAsync
註解)
這裏再提下使用異步方式處理事件的利弊,好處在於讓咱們程序在處理事件更加有效率,而缺點就在針對異常發生的處理更加複雜,須要藉助 AsyncUncaughtExceptionHandler
接口實現。
學習了那麼多 Spring Framework 的事件處理相關的內容後,咱們如今再來看看在 Spring Boot 裏事件處理有什麼須要額外學習的地方。仍是同樣,咱們先從 Spring Boot 官方文檔下手,在 Spring Boot
Doc 的 23.5 Application Events and Listeners 一節中提到了事件處理:
In addition to the usual Spring Framework events, such as
ContextRefreshedEvent
, aSpringApplication
sends some additional application events.Application events are sent by using Spring Framework’s event publishing mechanism.
能夠看出 Spring Boot 還是基於 Spring Framework 的事件發佈機制去處理事件,只是在此基礎了新增了幾個 SpringApplication
相關的事件:
ApplicationStartingEvent
:程序啓動時發生。ApplicationEnvironmentPreparedEvent
:程序中Environment
對象就緒時發生。ApplicationPreparedEvent
:程序啓動後但還未刷新時發生。ApplicationStartedEvent
:程序啓動刷新後發生。ApplicationReadyEvent
:程序啓動完畢,等待請求時發生。ApplicationFailedEvent
:程序啓動過程當中出現異常時發生。而且它們的執行順序也是列舉書順序依次觸發的。
另外,須要注意的是,當須要觸發的事件是在 ApplicationContext
建立以前發生時,用 @Bean
方式註冊的偵聽器就不會執行,而 Spring Boot 爲此提供了三種方式來處理這種狀況:
使用 SpringApplication.addListeners(…)
方法註冊偵聽器
SpringApplication springApplication = new SpringApplication(SpringEventsApplication.class);
springApplication.addListeners(new NormalCustomEventListener());
springApplication.run(args);
複製代碼
使用 SpringApplicationBuilder.listeners(…)
方法註冊偵聽器
SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder(SpringEventsApplication.class);
springApplicationBuilder.listeners(new NormalCustomEventListener()).run(args);
複製代碼
在應用資源文件夾新建文件 META-INF/spring.factories
,並將 org.springframework.context.ApplicationListener
做爲鍵,指定須要註冊的偵聽器類,如:
org.springframework.context.ApplicationListener=\
com.one.learn.spring.springevents.listener.NormalSecondCutomEventListener
複製代碼
到這裏咱們學習 Spring 事件相關的內容就結束了,瞭解 Spring 的事件機制,並適當應用,能夠爲咱們完成程序的某個功能時提供一個更加解耦,靈活的實現方式。
若是讀完以爲有收穫的話,歡迎點【好看】,點擊文章頭圖,掃碼關注【聞人的技術博客】😄😄😄。
Spring context-functionality-events: docs.spring.io/spring/docs…
Spring boot-features-application-events-and-listeners:docs.spring.io/spring-boot…
Spring Expression Language: docs.spring.io/spring/docs…
SpringEvents: www.baeldung.com/spring-even…
Better application events in Spring Framework 4.2: spring.io/blog/2015/0…