1. 概述
在本教程中,我們將討論如何使用 Spring 中的事件。
事件是框架中經常被忽視但同時也是非常有用的功能之一。 就像 Spring 中的許多其他事物一樣,事件發佈是 ApplicationContext 提供的能力之一。
以下是一些簡單的遵循指南:
- 如果使用 Spring Framework 版本低於 4.2,事件類應擴展 ApplicationEvent。 從 4.2 版本開始,事件類不再需要擴展 ApplicationEvent 類。
- 發佈器應注入 ApplicationEventPublisher 對象。
- 監聽器應實現 ApplicationListener 接口。
2. 自定義事件
Spring 允許我們創建和發佈自定義事件,默認情況下這些事件是同步的。 這具有幾個優點,例如監聽器可以參與發佈者的事務上下文。
2.1. 一個簡單的應用程序事件
讓我們創建一個簡單的事件類——僅作為存儲事件數據的佔位符。
在這種情況下,事件類存儲一個字符串消息:
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. 創建異步事件
在某些情況下,同步發佈事件可能不是我們所需要的——我們需要對我們的事件進行異步處理。
3.1. 使用 ApplicationEventMulticaster
我們可以通過創建帶有執行器的 ApplicationEventMulticaster Bean 來在配置中啓用異步事件處理。
對於我們這裏的目的,SimpleAsyncTaskExecutor 效果良好:
@Configuration
public class AsynchronousSpringEventsConfig {
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster =
new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
}事件、發佈者和監聽器實現與之前相同,但現在監聽器將在單獨的線程中異步處理事件。
然而,在某些情況下,我們可能無法使用多播器,或者更傾向於某些事件異步處理,而另一些事件則不異步處理。
3.2. 使用 <em @Async 標註
為了實現這一點,我們可以為需要異步處理事件的單個監聽器添加 Spring 的 <em @Async> 標註,從而識別和標註它們:
@EventListener
@Async
public void handleAsyncEvent(CustomSpringEvent event) {
System.out.println("Handle event asynchronously: " + event.getMessage());
}這段代碼會使用一個獨立的線程來處理事件。 此外,我們可以使用 <em >value</em > 屬性來指定 <em >@Async</em > 註解,從而指示使用除了默認的執行器之外的執行器,例如:
@Async("nonDefaultExecutor")
void handleAsyncEvent(CustomSpringEvent event) {
// run asynchronously by "nonDefaultExecutor"
}為了啓用對 @Async註解的支持,可以在 @Configuration或@SpringBootApplication類中添加@EnableAsync。
@Configuration
@EnableAsync
public class AppConfig {
}@EnableAsync 註解啓用 Spring 在後台線程池中運行 @Async 方法的能力。它還自定義了使用的 Executor。Spring 會搜索一個關聯的線程池定義。它會查找以下內容之一:
- 一個唯一的 TaskExecutor bean 在上下文中,或
- 一個名為 taskExecutor 的 Executor bean
如果它沒有找到任何一個,則將使用 SimpleAsyncTaskExecutor 來異步調用事件監聽器。
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 接口——它可以通過 @EventListener 標註,註冊到任何管理 Bean 的 public 方法上。
@Component
public class AnnotationDrivenEventListener {
@EventListener
public void handleContextStart(ContextStartedEvent cse) {
System.out.println("Handling context started event.");
}
}如前所述,方法簽名聲明瞭它所消費的事件類型。
默認情況下,監聽器是同步調用的。但是,我們可以通過添加 @Async 註解輕鬆使其變為異步。我們只需要記住在應用程序中啓用 EnableAsync 支持。
6. 通用類型支持
還可以使用泛型信息來分發事件,在事件類型中。
6.1. 通用應用程序事件
讓我們創建一個通用的事件類型。
在我們的示例中,事件類可以包含任何內容以及一個 成功狀態指示器:
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());
}
}不過,這個定義不幸地要求我們從 GenericSpringEvent 類繼承 ApplicationEvent 類。因此,對於本教程,讓我們利用之前討論的基於註解的事件監聽器 如前所述。
它也可能通過在 @EventListener 註解上定義一個布爾類型的 SpEL 表達式來使事件監聽器具有條件性。
在這種情況下,事件處理程序僅會在成功的 GenericSpringEvent 中被調用,並且該事件類型為 String:
@Component
public class AnnotationDrivenEventListener {
@EventListener(condition = "#event.success")
public void handleSuccessful(GenericSpringEvent<String> event) {
System.out.println("Handling generic event (conditional).");
}
}Spring 表達式語言 (SpEL) 是一種功能強大的表達式語言,詳細內容請參考另一篇教程。
6.3. 發佈器
發佈器與上面描述的發佈器類似 (見上文)。但由於類型擦除,我們需要發佈一個解決泛型參數的事件,例如,class GenericStringSpringEvent extends GenericSpringEvent<String>。
此外,還有一種發佈事件的替代方法。如果我們在帶有 @EventListener 註解的方法中返回一個非空值作為結果,Spring 框架會將該結果作為新的事件發送給我們。 此外,我們可以通過將它們作為事件處理結果在集合中返回,以發佈多個新的事件。
7. 事務邊界事件
本節介紹如何使用 @TransactionalEventListener 註解。要了解更多關於事務管理的知識,請查看《與 Spring 和 JPA 一起使用事務》。
自 Spring 4.2 版本起,框架提供了新的 @TransactionalEventListener 註解,它是 @EventListener 的擴展,允許將事件監聽器綁定到事務的階段。
綁定可以針對以下四個事務階段之一:
- AFTER_COMMIT (默認) – 用於在事務 成功完成 後觸發事件
- AFTER_ROLLBACK – 如果事務已回滾,則觸發事件
- AFTER_COMPLETION – 如果事務已 完成 (是 AFTER_COMMIT 和 AFTER_ROLLBACK 的別名),則觸發事件
- BEFORE_COMMIT – 用於在事務 提交 之前 立即 觸發事件
默認情況下,此監聽器僅在 CustomSpringEvent 已發佈並且位於已完成的事務中時會被調用。
@TransactionalEventListener
public void handleCustom(CustomSpringEvent event) {
System.out.println("Handling event only when a transaction successfully completes.");
}至關重要的是理解,與常規的 @EventListener 方法不同,@TransactionalEventListener 不會通過 ApplicationEventMulticaster 發送事件處理。相反,@TransactionalEventListener 通過 https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/support/TransactionSynchronizationManager.html#registerSynchronization(org.springframework.transaction.support.TransactionSynchronization) 註冊事務同步回調,從而允許事件在指定事務階段進行處理。
因此,@TransactionalEventListener 方法默認在發佈事件的同一線程中執行,無論多線程執行器在多線程器中是否應用,因為多線程器在這種情況下根本未被使用。
這導致了一個常見問題:我們如何使 @TransactionalEventListener 異步處理事件?
答案是使用 @Async 註解,例如:
@Async
@TransactionalEventListener
void handleCustom(CustomSpringEvent event) {
System.out.println("Handling event only when a transaction successfully completes.");
}在上述示例中,我們結合了 @Async 和 @TransactionalEventListener 註解。 這樣,當原始事務成功完成後,handleCustom() 方法將在一個獨立的線程中異步運行。
雖然這種方式可以方便地異步處理事務事件,但重要的是要謹慎使用它。 Spring 將事務綁定到當前線程。 當監聽器由於 @Async 在單獨的線程中運行而執行時,它無法訪問原始事務上下文。 這意味着 我們不應該使用 @Async + @TransactionalEventListener,如果我們的事件處理程序依賴於原始事務的上下文,例如延遲加載的實體、共享數據庫狀態或事務回滾邏輯。
當然,當我們使用 @Async 時,請務必添加 @EnableAsync 支持。
8. 結論
在本文中,我們簡要介紹了處理 Spring 事件的基礎知識,包括創建簡單的自定義事件、發佈事件以及在監聽器中處理事件。我們還對如何在配置中啓用事件的異步處理進行了簡要的探討。隨後,我們學習了 Spring 4.2 中引入的改進,例如註解驅動的監聽器、更好的泛型支持以及事件綁定到事務階段。