本文轉載自:阿里大牛親薦:SpringBoot應用程序事件教程,面試Spring你掌握多少
如果要“監聽”事件,我們可以在事件發生源處編寫“監聽器”來監聽事件,但會將事件源與偵聽器的邏輯緊密耦合。我們可以根據需要動態註冊和註銷某些事件的偵聽器。對於同一事件,我們也可以有多個偵聽器。本教程概述瞭如何發佈和監聽自定義事件,並解釋了Spring Boot的內置事件。
事件與直接方法調用
事件和直接方法調用都適合於不同的情況。對於方法調用,這就像斷言一樣,無論發送和接收模塊的狀態如何,他們都需要知道此事件的發生。
另一方面,對於事件,我們只是說發生了一個事件,並且通知了哪些模塊不是我們關心的問題。當我們想將處理傳遞給另一個線程時,最好使用事件(例如:在完成某些任務時發送電子郵件)。同樣,事件對於測試驅動的開發非常有用。
事件用於在鬆耦合的組件之間交換信息。由於發佈者和訂閱者之間沒有直接耦合,因此我們可以修改訂閱者而不影響發佈者,反之亦然。讓我們看看如何在Spring Boot應用程序中創建,發佈和收聽自定義事件。
1. 創建一個 ApplicationEvent
我們可以使用Spring Framework的事件發佈機制來發布應用程序事件。
讓我們創建一個UserCreatedEvent通過擴展調用的自定義事件ApplicationEvent:
class UserCreatedEvent extends ApplicationEvent {
private String name;
UserCreatedEvent(Object source, String name) {
super(source);
this.name = name;
}
...
}
source對象是事件發生時可以初始化和傳遞的參數,傳遞道super()方法。
從Spring 4.2開始,我們還可以將對象直接發佈爲事件,而無需擴展ApplicationEvent:
class UserRemovedEvent {
private String name;
UserRemovedEvent(String name) {
this.name = name;
}
...
}
2.發佈一個 ApplicationEvent
我們使用ApplicationEventPublisher接口來發布事件:
@Component
class Publisher {
private final ApplicationEventPublisher publisher;
Publisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
void publishEvent(final String name) {
// Publishing event created by extending ApplicationEvent
publisher.publishEvent(new UserCreatedEvent(this, name));
// Publishing an object as an event
publisher.publishEvent(new UserRemovedEvent(name));
}
}
當我們發佈的對象不是ApplicationEvent時,Spring會自動用PayloadApplicationEvent包裝它
3. 監聽事件
現在我們知道如何創建和發佈自定義事件,讓我們看看如何監聽事件。一個事件可以有多個偵聽器根據應用程序需求執行不同的工作。
有兩種定義偵聽器的方法。我們可以使用@EventListener註釋或實現ApplicationListener接口。無論哪種情況,監聽器類都必須由Spring管理。
從Spring 4.1開始,現在可以簡單地註釋託管bean的方法,@EventListener以自動註冊ApplicationListener與該方法的簽名匹配的方法:
@Component
class UserRemovedListener {
@EventListener
ReturnedEvent handleUserRemovedEvent(UserRemovedEvent event) {
// handle UserRemovedEvent ...
return new ReturnedEvent();
}
@EventListener
void handleReturnedEvent(ReturnedEvent event) {
// handle ReturnedEvent ...
}
...
}
啓用註釋驅動的配置時,不需要其他配置。我們的方法可以監聽多個事件,或者如果我們想完全不使用任何參數來定義它,那麼事件類型也可以在註釋本身上指定。範例:@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})。
對於帶有註釋@EventListener的方法的返回類型如定義爲非void,Spring會將結果作爲新事件發佈給我們。在上面的示例中,ReturnedEvent第一種方法返回的結果將被髮布,然後由第二種方法處理。
如果指定SpEL,Spring僅在某些情況下允許觸發我們的偵聽器condition:
@Component
class UserRemovedListener {
@EventListener(condition = "#event.name eq 'reflectoring'")
void handleConditionalListener(UserRemovedEvent event) {
// handle UserRemovedEvent
}
}
僅當表達式的計算結果爲true,或包含以下字符串之一時:“true”, “on”, “yes”, 或“1”.方法參數通過其名稱公開。條件表達式還公開了一個引用了raw ApplicationEvent(#root.event)和實際方法參數的“根”變量(#root.args)
在以上示例中,UserRemovedEvent僅當#event.name的值爲時’reflectoring’,纔會觸發偵聽器。
偵聽事件的另一種方法是實現ApplicationListener接口:
@Component
class UserCreatedListener implements ApplicationListener<UserCreatedEvent> {
@Override
public void onApplicationEvent(UserCreatedEvent event) {
// handle UserCreatedEvent
}
}
只要偵聽器對象在Spring應用程序上下文中註冊,它就會接收事件。當Spring路由一個事件時,它使用偵聽器的簽名來確定它是否與事件匹配。
異步事件監聽器
默認情況下,spring事件是同步的,這意味着發佈者線程將阻塞,直到所有偵聽器都完成對事件的處理爲止。
要使事件偵聽器以異步模式運行,我們要做的就是@Async在該偵聽器上使用註釋:
@Component
class AsyncListener {
@Async
@EventListener
void handleAsyncEvent(String event) {
// handle event
}
}
爲了使@Async註釋生效,我們還必須註釋一個@Configuration類,使用@EnableAsync註釋SpringBootApplication類。
上面的代碼示例還顯示,我們可以將String用作事件。使用風險自負。最好使用特定於我們用例的數據類型,以免與其他事件衝突。
事務綁定事件
Spring允許我們將事件偵聽器綁定到當前事務的某個階段。噹噹前事務的結果對偵聽器很重要時,這使事件可以更靈活地使用。
當我們使用註釋我們的方法時@TransactionalEventListener,我們得到了一個擴展的事件監聽器,該監聽器知道事務:
@Component
class UserRemovedListener {
@TransactionalEventListener(phase=TransactionPhase.AFTER_COMPLETION)
void handleAfterUserRemoved(UserRemovedEvent event) {
// handle UserRemovedEvent
}
}
UserRemovedListener 僅在當前事務完成時才調用。
我們可以將偵聽器綁定到事務的以下階段:
- AFTER_COMMIT:成功提交事務後,將處理該事件。如果事件偵聽器僅在當前事務成功時才運行,則可以使用此方法。
- AFTER_COMPLETION:在事務提交或回滾時將處理該事件。例如,我們可以使用它在事務完成後執行清理。
- AFTER_ROLLBACK:交易回滾後,將處理該事件。
- BEFORE_COMMIT:事件將在事務提交之前處理。例如,我們可以使用它來將事務性O / R映射會話刷新到數據庫。
Spring Boot的應用程序事件
以上是Spring事件,Spring Boot提供了幾個預定義ApplicationEvent的,這些預定義綁定到SpringApplication生命週期。
在ApplicationContext創建之前會觸發一些事件,因此我們無法將這些事件註冊爲@Bean。我們可以通過手動添加偵聽器來註冊這些事件的偵聽器:
@SpringBootApplication
public class EventsDemoApplication {
public static void main(String[] args) {
SpringApplication springApplication =
new SpringApplication(EventsDemoApplication.class);
springApplication.addListeners(new SpringBuiltInEventsListener());
springApplication.run(args);
}
}
通過將META-INF/spring.factories文件添加到我們的項目中,我們還可以註冊偵聽器,而不管如何創建應用的。並通過以下org.springframework.context.ApplicationListener鍵引用偵聽器:
org.springframework.context.ApplicationListener= com.reflectoring.eventdemo.SpringBuiltInEventsListener
class SpringBuiltInEventsListener
implements ApplicationListener<SpringApplicationEvent>{
@Override
public void onApplicationEvent(SpringApplicationEvent event) {
// handle event
}
}
一旦確保正確註冊了事件監聽器,我們就可以監聽所有Spring Boot的SpringApplicationEvents。讓我們按照它們應用程序啓動期間的執行順序來看看:
①. ApplicationStartingEvent
ApplicationStartingEvent在運行開始時但在任何處理之前都會觸發,除了偵聽器和初始化程序的註冊外。
②. ApplicationEnvironmentPreparedEvent
當Environment在上下文中是可用的,一個ApplicationEnvironmentPreparedEvent被觸發,由於此時Environment將準備就緒,因此我們可以在其他bean使用它之前對其進行檢查和修改。
③. ApplicationContextInitializedEvent
ApplicationContext已準備就緒時,一個ApplicationContextInitializedEvent觸發,ApplicationContextInitializers被稱爲尚未加載bean定義。在bean初始化到Spring容器之前,我們可以使用它執行任務。
④. ApplicationPreparedEvent
當ApllicationContext準備就緒時,一個ApplicationPreparedEvent時會觸發,但不會刷新。
在準備好的Environment和bean定義將被加載。
⑤. ContextRefreshedEvent
當ApplicationContext刷新時,ContextRefreshedEvent會觸發。
ContextRefreshedEvent是直接來自Spring,而不是Spring Boot,並不繼承擴展SpringApplicationEvent。
⑥. WebServerInitializedEvent
如果我們使用的是Web服務器,WebServerInitializedEvent則在Web服務器準備就緒後會觸發a。ServletWebServerInitializedEvent和ReactiveWebServerInitializedEvent分別是servlet和反應式變量。
WebServerInitializedEvent不是繼承擴展SpringApplicationEvent。
⑦. ApplicationStartedEvent
上下文已被刷新之後,一個ApplicationStartedEvent觸發,但在任何Spring boot應用程序和命令行運行都被調用前。
⑧. ApplicationReadyEvent
一個ApplicationReadyEvent觸發時就表示該應用程序已準備好服務請求。
建議此時不要修改內部狀態,因爲所有初始化步驟都將完成。
⑨. ApplicationFailedEvent
一個ApplicationFailedEvent如果有異常,應用程序無法啓動點火。在啓動期間的任何時間都可能發生這種情況。我們可以使用它來執行一些任務,例如執行腳本或在啓動失敗時發出通知。
結論
事件被設計爲在同一應用程序上下文中在Spring bean之間進行簡單的通信。從Spring 4.2開始,基礎結構已得到顯着改進,並提供了基於註釋的模型以及發佈任意事件的功能