[譯]談談SpringBoot 事件機制

東溪陳姓少年發表於2020-04-20

要“監聽”事件,我們總是可以將“監聽器”作為事件源中的另一個方法寫入事件,但這將使事件源與監聽器的邏輯緊密耦合。

對於實際事件,我們比直接方法呼叫更靈活。我們可以根據需要動態註冊和登出某些事件的偵聽器。我們還可以為同一事件設定多個偵聽器。

本教程概述瞭如何釋出和偵聽自定義事件,並解釋了 Spring Boot 的內建事件。


為什麼我應該使用事件而不是直接方法呼叫?

事件和直接方法呼叫都適合於不同的情況。使用方法呼叫,就像斷言一樣-無論傳送和接收模組的狀態如何,他們都需要知道此事件的發生。

對於事件,另一方面,我們只知道發生了一個事件,哪些模組會被通知並不是我們關心的問題。當我們想要將某些業務處理傳遞給另一個執行緒時(例如:在某些任務完成時傳送電子郵件),最好使用事件。此外,事件對於測試驅動的開發也很有用。

什麼是應用程式事件( Application Events)?

Spring 應用程式事件允許我們傳送和接收特定應用程式事件,我們可以根據需要處理這些事件。事件用於在鬆散耦合的元件之間交換資訊。由於釋出者和訂閱者之間沒有直接耦合,因此可以在不影響釋出者的情況下修改訂閱者,反之亦然。

讓我們看看如何在 Spring Boot 應用程式中建立、釋出和偵聽自定義事件。

建立ApplicationEvent

我們可以使用 Spring Framework 的事件釋出機制釋出應用程式事件。

讓我們通過擴充套件來建立呼叫的自定義事件:

class UserCreatedEvent extends ApplicationEvent {
  private String name;

  UserCreatedEvent(Object source, String name) {
    super(source);
    this.name = name;
  }
  ...
}

程式碼中super(source)中的source應該是最初發生事件的物件或與事件相關聯的物件。

從Spring 4.2開始,我們還可以將物件釋出為事件,而無需擴充套件ApplicationEvent:

class UserRemovedEvent {
  private String name;

  UserRemovedEvent(String name) {
    this.name = name;
  }
  ...
}

釋出一個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中。

接收應用程式事件

現在,我們知道如何建立和釋出自定義事件,讓我們看看如何偵聽該事件。事件可以有多個偵聽器並且根據應用程式要求執行不同的工作。

有兩種方法可以定義偵聽器。我們可以使用註解(@EventListener)或實現介面(ApplicationListener)。在這兩種情況下,偵聽器類都必須由 Spring 管理。

註解

從Spring 4.1開始,可以使用@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註解並定義為具有返回型別的方法,Spring會將結果作為新事件釋出給我們。在上面的示例中,第一個方法返回的ReturnedEvent將被髮布,然後由第二個方法處理。

如果指定SpEL條件,Spring僅在某些情況下才允許觸發我們的偵聽器:

@Component
class UserRemovedListener {

  @EventListener(condition = "#event.name eq 'reflectoring'")
  void handleConditionalListener(UserRemovedEvent event) {
    // handle UserRemovedEvent
  }
}

僅當表示式的計算結果為true或以下字串之一時才處理該事件:“ true”,“ on”,“ yes”或“ 1”。方法引數通過其名稱公開。條件表示式還公開了一個“ root”變數,該變數引用原始ApplicationEvent(#root.event)和實際方法引數(#root.args)

在以上示例中,僅當#event.name的值為'reflectoring'時,才會使用UserRemovedEvent觸發監聽器。

實現ApplicationListener介面

偵聽事件的另一種方法是實現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註解起作用,我們還必須使用@EnableAsync註解我們的@Configuration類之一或@SpringBootApplication類。

上面的程式碼示例還顯示了我們可以將String用作事件。使用風險自負。最好使用特定於我們用例的資料型別,以免與其他事件衝突。

Transaction-繫結事件

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:該事件將在事務提交之前進行處理。例如,我們可以使用它來將事務性ORM會話重新整理到資料庫。

Spring Boot的 Application Events

Spring Boot提供了幾個與SpringApplication生命週期相關的預定義ApplicationEvent。

在建立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

當上下文中使用的環境可用時,將觸發ApplicationEnvironmentPreparedEvent。

由於此時環境已準備就緒,因此我們可以在其他Bean使用它之前對其進行檢查和修改。

ApplicationContextInitializedEvent

當ApplicationContext準備就緒並且呼叫ApplicationContextInitializers但尚未載入bean定義時,將觸發ApplicationContextInitializedEvent。

在bean初始化到Spring容器之前,我們可以使用它來執行任務。

ApplicationPreparedEvent

準備好ApllicationContext但未重新整理時會觸發ApplicationPreparedEvent。

該環境已準備就緒,可以使用,並且將載入Bean定義。

WebServerInitializedEvent

如果我們使用的是網路伺服器,則在網路伺服器準備就緒後會觸發WebServerInitializedEvent。 ServletWebServerInitializedEvent和ReactiveWebServerInitializedEvent分別是servlet和反應式網路服務。

WebServerInitializedEvent不擴充套件SpringApplicationEvent。

ApplicationStartedEvent

在重新整理上下文之後但在呼叫任何應用程式和命令列執行程式之前,將觸發ApplicationStartedEvent。

ApplicationReadyEvent

觸發ApplicationReadyEvent來指示該應用程式已準備就緒,可以處理請求。

建議此時不要修改內部狀態,因為所有初始化步驟都將完成。

ApplicationFailedEvent

如果存在異常並且應用程式無法啟動,則會觸發ApplicationFailedEvent。在啟動期間的任何時間都可能發生這種情況。

我們可以使用它來執行一些任務,例如執行指令碼或在啟動失敗時發出通知。

結論

事件是為在同一應用程式上下文內的Spring Bean之間進行簡單通訊而設計的。從Spring 4.2開始,基礎結構已得到顯著改進,並提供了基於註釋的模型以及釋出任意事件的功能。

英文原文:https://reflectoring.io/spring-boot-application-events-explained/


關注筆者公眾號,推送各類原創/優質技術文章 ⬇️

WechatIMG6

相關文章