1. 程式人生 > >[譯]談談SpringBoot 事件機制

[譯]談談SpringBoot 事件機制

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

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

本教程概述瞭如何釋出和偵聽自定義事件,並解釋了 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/


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

相關推薦

[]談談SpringBoot 事件機制

要“監聽”事件,我們總是可以將“監聽器”作為事件源中的另一個方法寫入事件,但這將使事件源與監聽器的邏輯緊密耦合。 對於實際事件,我們比直接方法呼叫更靈活。我們可以根據需要動態註冊和登出某些事件的偵聽器。我們還可以為同一事件設定多個偵聽器。 本教程概述瞭如何釋出和偵聽自定義事件,並解釋了 Spring Boot

[SpringBoot] Spring的事件機制

直接上程式碼: 一.定義事件 public class MyEvent extends ApplicationEvent { private String msg; public MyEvent(Object source, String msg) {

SpringBoot-SpringBoot中的事件機制

微信公眾號:glmapper工作室 掘金專欄:glmapper 微 博:瘋狂的石頭_henu 歡迎關注,一起學習分享技術 在這篇文章中聊一聊 Spring 中的擴充套件機制(一)中對Spring中的事件機制進行了分析。那麼對於 SpringBoot 來說,它在 Spring 的基礎上又做了哪些拓展

【玩轉SpringBoot】通過事件機制參與SpringBoot應用的啟動過程

生命週期和事件監聽一個應用的啟動過程和關閉過程是歸屬到“生命週期”這個概念的範疇。典型的設計是在啟動和關閉過程中會觸發一系列的“事件”,我們只要監聽這些事件,就能參與到這個過程中來。要想監聽事件,首先得有事件監聽器,就是常說的Listener。下面就是Sprin

SpringBoot事件監聽機制原始碼分析(上) SpringBoot原始碼(九)

SpringBoot中文註釋專案Github地址: https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 本篇接 SpringApplication物件是如何構建的? SpringBoot原始碼(八) 1 溫故而知新 溫故而知新,我們來簡單回顧一下上篇

SpringBoot事件監聽機制及觀察者模式/釋出訂閱模式

[toc] ## 本篇要點 - 介紹觀察者模式和釋出訂閱模式的區別。 - SpringBoot快速入門事件監聽。 ## 什麼是觀察者模式? 觀察者模式是經典行為型設計模式之一。 在GoF的《設計模式》中,觀察者模式的定義:**在物件之間定義一個一對多的依賴,當一個物件狀態改變的時候,所有依賴的物件都會自

C#內置的事件機制和Unity3D姻緣

mono 需要 listen 存在 logs sharp strong 關聯 有一種 最近因為項目,也因為一些其他事情而導致學習的停止,抽個空來記錄下C#內置的事件在Unity3D中的使用。 我需要讓一個物體對鼠標懸停做出事件的響應的情況下,我們通常會創建一個繼承Mon

node.js零基礎詳細教程(4):node.js事件機制、node異步IO操作

nod server nbsp node i++ 兩個 con 錯誤 定時器 第四章 建議學習時間3小時 課程共10章 學習方式:詳細閱讀,並手動實現相關代碼 學習目標:此教程將教會大家 安裝Node、搭建服務器、express、mysql、mongodb、編寫後臺業務邏

PHP實現事件機制實例分析

word-wrap [0 popu except switch targe att otto 對象 PHP實現事件機制實例分析 內置了事件機制的語言不多,php也沒有提供這種功能。事件(Event)說簡單了就是一個Observer模式。實現起來非常ea

node.js之事件機制

參數說明 處理 emit tab 大數 參數 事件綁定 綁定 所有 EventEmitter類 方法名與參數描述參數說明 addListener(event,listener) 對指定的事件綁定事件處理函數 參數一是事件名稱,參數二是事件處理函數 on(event

Qt事件機制(是動作發生後,一種通知對象的消息,是被動與主動的總和。先處理自己隊列中的消息,然後再處理系統消息隊列中的消息)

str 發送 filter 簡化 後者 nts 類型 min() 鼠標滾輪 Qt事件機制 Qt程序是事件驅動的, 程序的每個動作都是由幕後某個事件所觸發.。 Qt事件的發生和處理成為程序運行的主線,存在於程序整個生命周期。 Qt事件的類型很多, 常見的qt的事件如下:

js事件機制

image blog div clas http com alt bubuko 機制 js事件屬性: js事件機制

小程序開發--小程序的事件機制

nta 單純 添加 cli nim ati 我們 -- api   首先在小程序中,如果要實現點擊操作,是沒有click事件,在官方api文檔中,給我們提供了tap事件來表示用戶短暫點擊後手指離開。   而如果我們要監聽這個事件,並且在這個事件上綁定一個方法,就要在在相應的

談談事件的理解(持續更新中)

use 通過 行為 tlist 理解 scrip 創建 可能 有時 談談對事件的理解: 從前有一家人,我們稱為window家。window他其中有一個兒子叫事件。 -------------------------------------------------------

NodeJS學習筆記 (21)事件機制-events(ok)

source 理解 SM 執行 hub 單個 nodejs index com 模塊概覽 events模塊是node的核心模塊之一,幾乎所有常用的node模塊都繼承了events模塊,比如http、fs等。 模塊本身非常簡單,API雖然也不少,但常用的就那麽幾個,這裏舉幾個

DOM事件機制(事件捕獲和事件冒泡和事件委托)

使用 tar web strong 事件 所有 span click ner 內容: 1.事件復習 2.事件冒泡與事件捕獲 3.事件委托 1.事件復習 (1)事件 事件是用來處理響應的一個機制,這個響應可以來自於用戶(點擊, 鼠標移動, 滾動), 也可以來自於瀏

Redis源碼閱讀(一)事件機制

port nbsp 分布式數據庫 同步 對數 lse concrete 編程 hat Redis源碼閱讀(一)事件機制 Redis作為一款NoSQL非關系內存數據庫,具有很高的讀寫性能,且原生支持的數據類型豐富,被廣泛的作為緩存、分布式數據庫、消息隊列等應用。此外Redis

jQuery事件機制,動畫效果,工具和其他操作(三)

不同 匹配 fad namespace event 返回 animate sin 繼續 jQuery事件機制 1 事件操作 1.1 頁面載入事件 $(document).ready(function(){ // 在這裏寫你的代碼... }); 或者 $(function

瀏覽器事件機制

class 觸發 highlight inner med content classname tel iat 事件被觸發三階段   1. document往事件觸發處傳播,會觸發遇到註冊的捕獲事件;   2. 傳播到事件觸發處,觸發註冊事件;   3. 從事件觸發處往doc