扒去Spring事件監聽機制的外衣,竟然是觀察者模式
前言
Spring中提供了一套預設的事件監聽機制,在容器初始化時便使用了這套機制。同時,Spring也提供了事件監聽機制的介面擴充套件能力,開發者基於此可快速實現自定義的事件監聽功能。
Spring的事件監聽機制是在JDK事件監聽的基礎上進行的擴充套件,也是在典型觀察者模式上的進一步抽象和改進。所以,結合Spring的事件監聽機制與觀察者模式來學習,可以達到理論與實踐的完美融合。
本篇文章就以觀察者模式和Spring事件監聽機制作為切入點,結合具體的例項來對兩者進行系統的學習和實踐。
觀察者模式
觀察者模式(Observer Pattern),也叫作釋出-訂閱模式(Publish/Subscribe)。
無論是觀察者模式,還是Spring的事件監聽機制,本質上都是在定義物件間一對多的依賴關係,使得每當一個物件(被觀察者/事件)改變狀態時,所有依賴於它的物件(觀察者/事件監聽器)都會得到通知,並被自動更新。
觀察者模式的優點在於:觀察者和被觀察者之間是抽象耦合,不管是新增觀察者或是被觀察者,都非常容易擴充套件。這也符合物件導向所倡導的“開閉原則”:對擴充套件開放,對修改關閉。
觀察者模式適用於以下三類場景:
關聯行為場景,而且關聯是可拆分的。 事件多級觸發場景。 跨系統的訊息交換場景,比如訊息佇列的處理機制。
在使用的過程中,也要綜合考慮開發效率和執行效率的問題。通常,一個被觀察者會對應多個觀察者,那麼在開發和除錯的過程中會有一定的複雜度。
同時,因為被觀察者存在關聯、多級拆分,也就是會有多個觀察者,而Java訊息的通知(和Spring的事件監聽機制)預設是順序執行的,如果其中一個觀察者執行時間過長或卡死,勢必會影響整體的效率。此時,就需要考慮非同步處理。
觀察者的角色定義
觀察者模式是一個典型的釋出-訂閱模型,其中主要涉及四個角色:
抽象被觀察者角色:內部持有所有觀察者角色的引用,並對外提供新增、移除觀察者角色、通知所有觀察者的功能; 具體被觀察者角色:當狀態變更時,會通知到所有的觀察者角色; 抽象觀察者角色:抽象具體觀察者角色的一些共性方法,如狀態變更方法; 具體觀察者角色:實現抽象觀察者角色的方法;
UML類圖展示類觀察者模式大體如下:
以具體的程式碼來展示一下觀察者模式的實現。
第一,定義抽象觀察者。
/**
* 抽象觀察者角色
* @author sec
**/
public abstract class AbstractObserver {
/**
* 接收訊息
* @param context 訊息內容
*/
public abstract void receiveMsg(String context);
}
第二,定義抽象被觀察者。
/**
* 抽象主題(抽象被觀察者角色)
* @author sec
**/
public abstract class AbstractSubject {
/**
* 持有所有抽象觀察者角色的集合引用
*/
private final List<AbstractObserver> observers = new ArrayList<>();
/**
* 新增一個觀察者
* @param observer 觀察者
*/
public void addObserver(AbstractObserver observer){
observers.add(observer);
}
/**
* 移除一個觀察者
* @param observer 觀察者
*/
public void removeObserver(AbstractObserver observer){
observers.remove(observer);
}
/**
* 通知所有的觀察者,執行觀察者更新方法
* @param context 通知內容
*/
public void notifyObserver(String context){
observers.forEach(observer -> observer.receiveMsg(context));
}
}
第三,定義具體被觀察者,實現了抽象被觀察者。
/**
* 具體被觀察者
* @author sec
**/
public class ConcreteSubject extends AbstractSubject{
/**
* 被觀察者傳送訊息
* @param context 訊息內容
*/
public void sendMsg(String context){
System.out.println("具體被觀察者角色傳送訊息: " + context);
super.notifyObserver(context);
}
}
第四,定義具體觀察者,實現了抽象觀察者。
/**
* 具體觀察者角色實現類
* @author sec
**/
public class ConcreteObserver extends AbstractObserver{
@Override
public void receiveMsg(String context) {
System.out.println("具體觀察者角色接收訊息: " + context);
}
}
第五,使用演示類。
public class ObserverPatternTest {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
subject.addObserver(new ConcreteObserver());
subject.sendMsg("Hello World!");
}
}
執行上述方法,控制檯列印日誌為:
具體被觀察者角色傳送訊息: Hello World!
具體觀察者角色接收訊息: Hello World!
在上述程式碼實現中,被觀察者發出訊息後,觀察者接收到具體的訊息,如果新增了多個觀察者,它們均會收到訊息。也就是前面所說的,每當一個物件(被觀察者/事件)改變狀態時,所有依賴於它的物件(觀察者/事件監聽器)都會得到通知,並被自動更新。
Java中的事件機制
前面聊了觀察者模式,這裡再來看看Java中的事件機制。
在JDK 1.1及以後版本中,事件處理模型採用基於觀察者模式的委派事件模型(DelegationEvent Model, DEM),即一個Java元件所引發的事件並不由引發事件的物件自己來負責處理,而是委派給獨立的事件處理物件負責。
這並不是說事件模型是基於Observer和Observable的,事件模型與Observer和Observable沒有任何關係,Observer和Observable只是觀察者模式的一種實現而已。
Java中的事件機制有三個角色參與:
Event Source
:事件源,發起事件的主體。Event Object
:事件狀態物件,傳遞的資訊載體,可以是事件源本身,一般作為引數存在於listerner的方法之中。所有事件狀態物件都將從Java中的EventObject派生而來;Event Listener
:事件監聽器,當監聽到EventObject產生時,呼叫相應的方法進行處理。所有事件偵聽器介面必須擴充套件EventListener介面;
UML類圖展示類事件模式大體如下:
在上面的UML圖中,EventObject一般作為Listener處理方法的引數傳入,而EventSource是事件的觸發者,透過此物件註冊相關的Listener,然後向Listener觸發事件。
透過UML圖的對比可以看出,事件監聽模式和觀察者模式大同小異,它們屬於同一型別模式,都屬於回撥機制,主動推送訊息,但在使用場景上有所區別。
觀察者(Observer)相當於事件監聽者(監聽器),被觀察者(Observable)相當於事件源和事件,事件監聽比觀察者模式要複雜一些,多了EventSource角色的存在。
以具體的程式碼來展示一下Java中的事件機制實現。
第一,定義事件物件。
/**
* 事件物件
*
* @author sec
**/
public class DoorEvent extends EventObject {
private int state;
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public DoorEvent(Object source) {
super(source);
}
public DoorEvent(Object source, int state) {
super(source);
this.state = state;
}
// 省略getter/setter方法
}
第二,定義事件監聽器介面。
/**
* 事件監聽器介面
*
* @author sec
**/
public interface DoorListener extends EventListener {
/**
* 門處理事件
* @param doorEvent 事件
*/
void doorEvent(DoorEvent doorEvent);
}
第三,定義事件監聽器的實現類。
public class CloseDoorListener implements DoorListener{
@Override
public void doorEvent(DoorEvent doorEvent) {
if(doorEvent.getState() == -1){
System.out.println("門關上了");
}
}
}
public class OpenDoorListener implements DoorListener{
@Override
public void doorEvent(DoorEvent doorEvent) {
if(doorEvent.getState() == 1){
System.out.println("門開啟了");
}
}
}
這裡實現了門的開和關兩個事件監聽器類。
第四,定義事件源EventSource。
public class EventSource {
//監聽器列表,監聽器的註冊則加入此列表
private Vector<DoorListener> listenerList = new Vector<>();
//註冊監聽器
public void addListener(DoorListener eventListener) {
listenerList.add(eventListener);
}
//撤銷註冊
public void removeListener(DoorListener eventListener) {
listenerList.remove(eventListener);
}
//接受外部事件
public void notifyListenerEvents(DoorEvent event) {
for (DoorListener eventListener : listenerList) {
eventListener.doorEvent(event);
}
}
}
第五,測試類。
public class EventTest {
public static void main(String[] args) {
EventSource eventSource = new EventSource();
eventSource.addListener(new CloseDoorListener());
eventSource.addListener(new OpenDoorListener());
eventSource.notifyListenerEvents(new DoorEvent("關門事件", -1));
eventSource.notifyListenerEvents(new DoorEvent("開門時間", 1));
}
}
執行測試類,控制檯列印:
門關上了
門開啟了
事件成功觸發。
Spring中的事件機制
在瞭解了觀察者模式和Java的事件機制之後,再來看看Spring中的事件機制。在Spring容器中,透過ApplicationEvent
和ApplicationListener
介面來實現事件監聽機制。每次Event事件被髮布到Spring容器中,都會通知對應的Listener。預設情況下,Spring的事件監聽機制是同步的。
Spring的事件監聽由三部分組成:
事件(ApplicationEvent):該類繼承自JDK中的EventObject,負責對應相應的監聽器,事件源發生某事件是特定事件監聽器被觸發的原因; 監聽器(ApplicationListener):該類繼承自JDK中的EventListener,對應於觀察者模式中的觀察者。監聽器監聽特定事件,並在內部定義了事件發生後的響應邏輯; 事件釋出器(ApplicationEventPublisher):對應於觀察者模式中的被觀察者/主題,負責通知觀察者,對外提供釋出事件和增刪事件監聽器的介面,維護事件和事件監聽器之間的對映關係,並在事件發生時負責通知相關監聽器。
透過上面的分析可以看出Spring的事件機制不僅是觀察者模式的一種實現,也實現了JDK提供的事件介面。同時,除了釋出者和監聽者之外,還存在一個EventMulticaster的角色,負責把事件轉發給監聽者。
Spring事件機制的工作流程如下:
在上述流程中,釋出者呼叫applicationEventPublisher.publishEvent(msg),將事件傳送給EventMultiCaster。EventMultiCaster註冊著所有的Listener,它會根據事件型別決定轉發給那個Listener。
在Spring中提供了一些標準的事件,比如:ContextRefreshEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent等。
關於Spring事件機制的具體實現和這些標準事件的作用,大家可以透過閱讀原始碼來學習,這裡不再詳細展開。
下面來看看Spring事件機制涉及到的幾個角色的原始碼及後續基於它們的實踐。
第一,事件(ApplicationEvent)。
public abstract class ApplicationEvent extends EventObject {
/** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = 7099057708183571937L;
/** System time when the event happened. */
private final long timestamp;
/**
* Create a new {@code ApplicationEvent}.
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
/**
* Return the system time in milliseconds when the event occurred.
*/
public final long getTimestamp() {
return this.timestamp;
}
}
事件可類比觀察者中的被觀察者實現類的角色,繼承自JDK的EventObject。上述Spring中的標準事件都是直接或間接繼承自該類。
第二,事件釋出器(ApplicationEventPublisher)。
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
透過實現ApplicationEventPublisher介面,並重寫publishEvent()方法,可以自定義事件釋出的邏輯。ApplicationContext繼承了ApplicationEventPublisher介面。因此,我們可以透過實現ApplicationContextAware介面,注入ApplicationContext,然後透過ApplicationContext的publishEvent()方法來實現事件釋出功能。
ApplicationContext容器本身僅僅是對外提供了事件釋出的介面publishEvent(),真正的工作委託給了具體容器內部的ApplicationEventMulticaster物件。而ApplicationEventMulticaster物件可類比觀察者模式中的抽象被觀察者角色,負責持有所有觀察者集合的引用、動態新增、移除觀察者角色。
第三,事件監聽器(ApplicationListener)。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
事件監聽器(ApplicationListener)對應於觀察者模式中的具體觀察者角色。當事件釋出之後,就會執行事件監聽器的邏輯。透過實現ApplicationListener介面,並重寫onApplicationEvent()方法,就可以監聽到事件釋出器釋出的事件。
Spring事件監聽案例
下面以具體的案例程式碼來說明如何自定義實現Spring事件監聽。
第一,自定義定義事件物件,整合自ApplicationEvent。
public class MyEvent extends ApplicationEvent {
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public MyEvent(Object source) {
super(source);
}
private String context;
public MyEvent(Object source, String context){
super(source);
this.context = context;
}
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
}
}
第二,自定義ApplicationListener事件監聽器。
@Component
public class MyApplicationListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
// 監聽到具體事件,處理對應具體邏輯
System.out.println("event.getContext() = " + event.getContext());
}
}
除了上述基於實現ApplicationListener介面的方式外,還可以使用**@EventListener**註解來實現,實現示例如下:
@Component
public class MyApplicationListener{
// 透過註解實現監聽器
@EventListener
public void handleMyEvent(MyEvent event){
// 監聽到具體事件,處理對應具體邏輯
System.out.println("event.getContext() = " + event.getContext());
}
}
第三,使用及單元測試。
@Slf4j
@SpringBootTest
public class SpringEventTest {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Test
void testEvent() {
eventPublisher.publishEvent(new MyEvent("自定義事件", "Hello World!"));
}
}
執行單元測試,可看到控制檯列印對應的事件資訊。
透過上述方式我們已經成功實現了基於Spring的事件監聽機制,但這其中還有一個問題:同步處理。預設情況下,上述事件是基於同步處理的,如果其中一個監聽器阻塞,那麼整個執行緒將處於等待狀態。
那麼,如何使用非同步方式處理監聽事件呢?只需兩步即可。
第一步,在監聽器類或方法上新增@Async
註解,例如:
@Component
@Async
public class MyApplicationListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
// 監聽到具體事件,處理對應具體邏輯
System.out.println("event.getContext() = " + event.getContext());
}
}
第二步,在SpringBoot啟動類(這裡以SpringBoot專案為例)上新增@EnableAsync
註解,例如:
@SpringBootApplication
@EnableAsync
public class SpringBootMainApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootMainApplication.class, args);
}
}
此時,就可以實現非同步監聽功能了。當然,@Async
註解也可以指定我們已經配置好的執行緒池來處理非同步請求,關於執行緒數的初始化這裡就不再演示了。
小結
本篇文章帶大家從觀察者模式、Java事件機制延伸到Spring的事件監聽機制,將三者融合在一起來講解。透過這個案例,其實我們能夠體會到一些經驗性的知識,比如看似複雜的Spring事件監聽機制實現只不過是觀察者模式的一種實現,而其中又整合了Java的事件機制。這也就是所謂的融會貫通。
我們如果單純的學習某一個設計模式,可能只會運用和識別它的簡單實現,而實踐中往往會對設計模式進行變種,甚至融合多種設計模式的優點於一體,這便是活學活用。希望透過這邊文章你能夠更加深入的理解上述三者。
參考文章:
https://blog.csdn.net/Weixiaohuai/article/details/122367792
https://www.cnblogs.com/admol/p/14036564.html
https://blog.csdn.net/qq_30364247/article/details/123168800
https://cloud.tencent.com/developer/article/1701947
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2929239/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- SpringBoot事件監聽機制及觀察者模式/釋出訂閱模式Spring Boot事件模式
- 透過觀察者監聽模型事件模型事件
- 通過觀察者監聽模型事件模型事件
- Spring事件監聽機制原始碼解析Spring事件原始碼
- Spring事件釋出與監聽機制Spring事件
- Spring 事件監聽機制及原理分析Spring事件
- Spring筆記(7) - Spring的事件和監聽機制Spring筆記事件
- Spring中的觀察者模式Spring模式
- spring-event-事件監聽機制實現Spring事件
- iOS設計模式2 - 觀察者模式_通知機制iOS設計模式
- JS觀察者模式-自定義事件JS模式事件
- 觀察者模式,無需多執行緒完成資料監聽模式執行緒
- 觀察者模式——從JDK到Spring模式JDKSpring
- Apache ZooKeeper - 事件監聽機制初探Apache事件
- 事件匯流排EventBus和觀察者模式事件模式
- 觀察者模式模式
- 三種方式實現觀察者模式 及 Spring中的事件程式設計模型模式Spring事件程式設計模型
- 觀察者模式與.Net Framework中的委託與事件模式Framework事件
- PHP觀察者模式PHP模式
- 觀察者模式(2)模式
- Unity——觀察者模式Unity模式
- 觀察者模式-將訊息通知給觀察者模式
- Spring Boot 事件和監聽Spring Boot事件
- 進擊的觀察者模式模式
- 設計模式 —— 觀察者模式設計模式
- 設計模式(觀察者模式)設計模式
- 設計模式----觀察者模式設計模式
- 【設計模式】觀察者模式設計模式
- 設計模式——觀察者模式設計模式
- 設計模式中的觀察者模式設計模式
- observer-觀察者模式Server模式
- 重構 - 觀察者模式模式
- 18_觀察者模式模式
- PHP-觀察者模式PHP模式
- 大話--觀察者模式模式
- PHP 之觀察者模式PHP模式
- redux與觀察者模式Redux模式
- 觀察者模式介紹模式