Spring Boot應用程式事件教程 - reflectoring

banq發表於2020-04-05

如果要“監聽”事件,我們可以在事件發生源處編寫“監聽器”來監聽事件,但會將事件源與偵聽器的邏輯緊密耦合。我們可以根據需要動態註冊和登出某些事件的偵聽器。對於同一事件,我們也可以有多個偵聽器。本教程概述瞭如何釋出和監聽自定義事件,並解釋了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開始,基礎結構已得到顯著改進,並提供了基於註釋的模型以及釋出任意事件的功能。
您可以在GitHub上找到示例程式碼。

相關文章