【注】本文譯自:Spring Events | Baeldung
1. 概述
在本教程中,我們將討論如何在 Spring 中使用事件。
事件是框架中最容易被忽視的功能之一,但也是更有用的功能之一。和 Spring 中的許多其他東西一樣,事件釋出是 ApplicationContext 提供的功能之一。
有一些簡單的指導方針可以遵循:
- 如果我們使用 Spring Framework 4.2 之前的版本,事件類應該擴充套件 ApplicationEvent。從 4.2 版本開始,事件類不再需要擴充套件 ApplicationEvent 類。
- 釋出者應該注入一個 ApplicationEventPublisher 物件。
監聽器應實現 ApplicationListener 介面。
2.自定義事件
Spring 允許我們建立和釋出預設同步的自定義事件。這有一些優點,例如監聽器能夠參與釋出者的事務上下文。
2.1.一個簡單的應用程式事件
讓我們建立一個簡單的事件類——只是一個用於儲存事件資料的佔位符。
在這種情況下,事件類包含一個 String 訊息:
public class CustomSpringEvent extends ApplicationEvent {
private String message;
public CustomSpringEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
2.2. 釋出者
現在讓我們建立該事件的釋出者。釋出者構造事件物件並將其釋出給正在收聽的任何人。
要釋出事件,釋出者可以簡單地注入 ApplicationEventPublisher 並使用 publishEvent() API:
@Component
public class CustomSpringEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void publishCustomEvent(final String message) {
System.out.println("Publishing custom event. ");
CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
applicationEventPublisher.publishEvent(customSpringEvent);
}
}
或者,釋出者類可以實現
ApplicationEventPublisherAware 介面,這也會在應用程式啟動時注入事件釋出者。通常,將 @Autowire 注入釋出者會更簡單。
從 Spring Framework 4.2 開始,ApplicationEventPublisher 介面為 publishEvent(Object event) 方法提供了一個新的過載,該方法接受任何物件作為事件。因此,Spring 事件不再需要擴充套件 ApplicationEvent 類。
2.3. 監聽器
最後,讓我們建立監聽器。
監聽器的唯一要求是是一個 bean 並實現 ApplicationListener 介面:
@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
@Override
public void onApplicationEvent(CustomSpringEvent event) {
System.out.println("Received spring custom event - " + event.getMessage());
}
}
請注意我們的自定義監聽器如何使用自定義事件的通用型別進行引數化,這使得 onApplicationEvent() 方法型別安全。這也避免了必須檢查物件是否是特定事件類的例項並對其進行轉換。
而且,正如已經討論過的(預設情況下,Spring 事件是同步的), doStuffAndPublishAnEvent() 方法會阻塞,直到所有監聽器完成對事件的處理。
3.建立非同步事件
在某些情況下,同步釋出事件並不是我們真正想要的——我們可能需要非同步處理我們的事件。
我們可以通過建立一個帶有執行程式的
ApplicationEventMulticaster bean 在配置中開啟它。
對於我們來說 SimpleAsyncTaskExecutor 很好地實現了這個目的:
@Configuration
public class AsynchronousSpringEventsConfig {
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
}
事件、釋出者和監聽器實現與以前相同,但現在監聽器將在單獨的執行緒中非同步處理事件。
4.現有框架事件
Spring 本身釋出了各種開箱即用的事件。例如,ApplicationContext 將觸發各種框架事件:ContextRefreshedEvent、ContextStartedEvent、RequestHandledEvent 等。
這些事件為應用程式開發人員提供了一個選項,可以連線到應用程式的生命週期和上下文,並在需要的地方新增他們自己的自定義邏輯。
這是監聽上下文重新整理的監聽器的快速示例:
public class ContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent cse) {
System.out.println("Handling context re-freshed event. ");
}
}
要了解有關現有框架事件的更多資訊,請在此處檢視我們的下一個教程。
5.註解驅動的事件監聽器
從 Spring 4.2 開始,事件監聽器不需要是實現 ApplicationListener 介面的 bean——它可以通過 @EventListener 註解在託管 bean 的任何 public 方法上註冊:
@Component
public class AnnotationDrivenEventListener {
@EventListener
public void handleContextStart(ContextStartedEvent cse) {
System.out.println("Handling context started event.");
}
}
和以前一樣,方法簽名宣告瞭它使用的事件型別。
預設情況下,監聽器是同步呼叫的。但是,我們可以通過新增 @Async 註釋輕鬆地使其非同步。我們只需要記住在應用程式中啟用非同步支援。
6.泛型支援
也可以使用事件型別中的泛型資訊來排程事件。
6.1. 泛型應用程式事件
讓我們建立一個泛型事件型別。
在我們的示例中,事件類包含任何內容和 success 狀態指示器:
public class GenericSpringEvent<T> {
private T what;
protected boolean success;
public GenericSpringEvent(T what, boolean success) {
this.what = what;
this.success = success;
}
// ... standard getters
}
請注意 GenericSpringEvent 和 CustomSpringEvent 之間的區別。我們現在可以靈活地釋出任意事件,並且不再需要從 ApplicationEvent 擴充套件。
6.2.監聽器
現在讓我們建立該事件的監聽器。
我們可以像以前一樣通過實現 ApplicationListener 介面來定義監聽器:
@Component
public class GenericSpringEventListener implements ApplicationListener<GenericSpringEvent<String>> {
@Override
public void onApplicationEvent(@NonNull GenericSpringEvent<String> event) {
System.out.println("Received spring generic event - " + event.getWhat());
}
}
但不幸的是,這個定義要求我們從 ApplicationEvent 類繼承 GenericSpringEvent。因此,對於本教程,讓我們使用之前討論過的註釋驅動事件監聽器。
通過在 @EventListener 註釋上定義布林 SpEL 表示式,也可以使事件監聽器有條件。
在這種情況下,只有成功呼叫 GenericSpringEvent 的 String 物件時才會呼叫事件處理程式:
@Component
public class GenericSpringEventListener implements ApplicationListener<GenericSpringEvent<String>> {
@Override
public void onApplicationEvent(@NonNull GenericSpringEvent<String> event) {
System.out.println("Received spring generic event - " + event.getWhat());
}
}
Spring 表示式語言 (SpEL)") 是一種強大的表示式語言,在另一篇教程中有詳細介紹。
6.3. 釋出者
事件釋出者與上述類似。但是由於型別擦除,我們需要釋出一個事件來解析我們將過濾的泛型引數,例如,類 GenericStringSpringEvent extends GenericSpringEvent<String>。
此外,還有一種釋出事件的替代方法。如果我們從使用 @EventListener 註解的方法返回一個非空值作為結果,Spring Framework 會將該結果作為新事件傳送給我們。此外,通過將多個新事件作為事件處理的結果返回到一個集合中,我們可以釋出多個新事件。
7.事務繫結事件
本節是關於使用 *@
TransactionalEventListener* 註解的。要了解有關事務管理的更多資訊,請檢視使用 Spring 和 JPA 事務。
從 Spring 4.2 開始,框架提供了一個新的 *@
TransactionalEventListener 註解,它是 @EventListener* 的擴充套件,它允許將事件的監聽器繫結到事務的某個階段。
可以繫結到以下事務階段:
- AFTER_COMMIT(預設)- 用於在事務成功完成時觸發事件。
- AFTER_ROLLBACK – 如果事務已回滾
- AFTER_COMPLETION – 如果事務已完成(AFTER_COMMIT 和 AFTER_ROLLBACK 的別名)
- BEFORE_COMMIT - 用於在事務提交之前觸發事件。
這是一個事務性事件監聽器的快速示例:
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleCustom(CustomSpringEvent event) {
System.out.println("Handling event inside a transaction BEFORE COMMIT.");
}
僅當存在事件生產者正在執行且即將提交的事務時,才會呼叫此監聽器。
如果沒有事務在執行,則根本不會傳送事件,除非我們通過將 fallbackExecution 屬性設定為 true 來覆蓋它。
8. 結論
在這篇簡短的文章中,我們介紹了在 Spring 中處理事件的基礎知識,包括建立一個簡單的自定義事件、釋出它,然後在監聽器中處理它。
我們還簡要了解了如何在配置中啟用事件的非同步處理。
然後我們瞭解了 Spring 4.2 中引入的改進,例如註解驅動的監聽器、更好的泛型支援和事件繫結到事務階段。
與往常一樣,本文中提供的程式碼可在 GitHub 上獲得。這是一個基於 Maven 的專案,因此它應該很容易匯入和執行。