Spring事件監(jiān)聽機(jī)制觀察者模式詳解
前言
Spring中提供了一套默認(rèn)的事件監(jiān)聽機(jī)制,在容器初始化時便使用了這套機(jī)制。同時,Spring也提供了事件監(jiān)聽機(jī)制的接口擴(kuò)展能力,開發(fā)者基于此可快速實(shí)現(xiàn)自定義的事件監(jiān)聽功能。
Spring的事件監(jiān)聽機(jī)制是在JDK事件監(jiān)聽的基礎(chǔ)上進(jìn)行的擴(kuò)展,也是在典型觀察者模式上的進(jìn)一步抽象和改進(jìn)。所以,結(jié)合Spring的事件監(jiān)聽機(jī)制與觀察者模式來學(xué)習(xí),可以達(dá)到理論與實(shí)踐的完美融合。
本篇文章就以觀察者模式和Spring事件監(jiān)聽機(jī)制作為切入點(diǎn),結(jié)合具體的實(shí)例來對兩者進(jìn)行系統(tǒng)的學(xué)習(xí)和實(shí)踐。
觀察者模式
觀察者模式(Observer Pattern),也叫作發(fā)布-訂閱模式(Publish/Subscribe)。
無論是觀察者模式,還是Spring的事件監(jiān)聽機(jī)制,本質(zhì)上都是在定義對象間一對多的依賴關(guān)系,使得每當(dāng)一個對象(被觀察者/事件)改變狀態(tài)時,所有依賴于它的對象(觀察者/事件監(jiān)聽器)都會得到通知,并被自動更新。
觀察者模式的優(yōu)點(diǎn)在于:觀察者和被觀察者之間是抽象耦合,不管是新增觀察者或是被觀察者,都非常容易擴(kuò)展。這也符合面向?qū)ο笏珜?dǎo)的“開閉原則”:對擴(kuò)展開放,對修改關(guān)閉。
觀察者模式適用于以下三類場景:
- 關(guān)聯(lián)行為場景,而且關(guān)聯(lián)是可拆分的。
- 事件多級觸發(fā)場景。
- 跨系統(tǒng)的消息交換場景,比如消息隊列的處理機(jī)制。
在使用的過程中,也要綜合考慮開發(fā)效率和運(yùn)行效率的問題。通常,一個被觀察者會對應(yīng)多個觀察者,那么在開發(fā)和調(diào)試的過程中會有一定的復(fù)雜度。
同時,因?yàn)楸挥^察者存在關(guān)聯(lián)、多級拆分,也就是會有多個觀察者,而Java消息的通知(和Spring的事件監(jiān)聽機(jī)制)默認(rèn)是順序執(zhí)行的,如果其中一個觀察者執(zhí)行時間過長或卡死,勢必會影響整體的效率。此時,就需要考慮異步處理。
觀察者的角色定義
觀察者模式是一個典型的發(fā)布-訂閱模型,其中主要涉及四個角色:
- 抽象被觀察者角色:內(nèi)部持有所有觀察者角色的引用,并對外提供新增、移除觀察者角色、通知所有觀察者的功能;
- 具體被觀察者角色:當(dāng)狀態(tài)變更時,會通知到所有的觀察者角色;
- 抽象觀察者角色:抽象具體觀察者角色的一些共性方法,如狀態(tài)變更方法;
- 具體觀察者角色:實(shí)現(xiàn)抽象觀察者角色的方法;
UML類圖展示類觀察者模式大體如下:
以具體的代碼來展示一下觀察者模式的實(shí)現(xiàn)。
第一,定義抽象觀察者。
/** * 抽象觀察者角色 * @author sec **/ public abstract class AbstractObserver { /** * 接收消息 * @param context 消息內(nèi)容 */ 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); } /** * 通知所有的觀察者,執(zhí)行觀察者更新方法 * @param context 通知內(nèi)容 */ public void notifyObserver(String context){ observers.forEach(observer -> observer.receiveMsg(context)); } }
第三,定義具體被觀察者,實(shí)現(xiàn)了抽象被觀察者。
/** * 具體被觀察者 * @author sec **/ public class ConcreteSubject extends AbstractSubject{ /** * 被觀察者發(fā)送消息 * @param context 消息內(nèi)容 */ public void sendMsg(String context){ System.out.println("具體被觀察者角色發(fā)送消息: " + context); super.notifyObserver(context); } }
第四,定義具體觀察者,實(shí)現(xiàn)了抽象觀察者。
/** * 具體觀察者角色實(shí)現(xiàn)類 * @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!"); } }
執(zhí)行上述方法,控制臺打印日志為:
具體被觀察者角色發(fā)送消息: Hello World!
具體觀察者角色接收消息: Hello World!
在上述代碼實(shí)現(xiàn)中,被觀察者發(fā)出消息后,觀察者接收到具體的消息,如果添加了多個觀察者,它們均會收到消息。也就是前面所說的,每當(dāng)一個對象(被觀察者/事件)改變狀態(tài)時,所有依賴于它的對象(觀察者/事件監(jiān)聽器)都會得到通知,并被自動更新。
Java中的事件機(jī)制
前面聊了觀察者模式,這里再來看看Java中的事件機(jī)制。
在JDK 1.1及以后版本中,事件處理模型采用基于觀察者模式的委派事件模型(DelegationEvent Model, DEM),即一個Java組件所引發(fā)的事件并不由引發(fā)事件的對象自己來負(fù)責(zé)處理,而是委派給獨(dú)立的事件處理對象負(fù)責(zé)。
這并不是說事件模型是基于Observer和Observable的,事件模型與Observer和Observable沒有任何關(guān)系,Observer和Observable只是觀察者模式的一種實(shí)現(xiàn)而已。
Java中的事件機(jī)制有三個角色參與:
Event Source
:事件源,發(fā)起事件的主體。
Event Object
:事件狀態(tài)對象,傳遞的信息載體,可以是事件源本身,一般作為參數(shù)存在于listerner的方法之中。所有事件狀態(tài)對象都將從Java中的EventObject派生而來;
Event Listener
:事件監(jiān)聽器,當(dāng)監(jiān)聽到EventObject產(chǎn)生時,調(diào)用相應(yīng)的方法進(jìn)行處理。所有事件偵 聽 器接口必須擴(kuò)展EventListener接口;
UML類圖展示類事件模式大體如下:
在上面的UML圖中,EventObject一般作為Listener處理方法的參數(shù)傳入,而EventSource是事件的觸發(fā)者,通過此對象注冊相關(guān)的Listener,然后向Listener觸發(fā)事件。
通過UML圖的對比可以看出,事件監(jiān)聽模式和觀察者模式大同小異,它們屬于同一類型模式,都屬于回調(diào)機(jī)制,主動推送消息,但在使用場景上有所區(qū)別。
觀察者(Observer)相當(dāng)于事件監(jiān)聽者(監(jiān)聽器),被觀察者(Observable)相當(dāng)于事件源和事件,事件監(jiān)聽比觀察者模式要復(fù)雜一些,多了EventSource角色的存在。
以具體的代碼來展示一下Java中的事件機(jī)制實(shí)現(xiàn)。
第一,定義事件對象。
/** * 事件對象 * * @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方法 }
第二,定義事件監(jiān)聽器接口。
/** * 事件監(jiān)聽器接口 * * @author sec **/ public interface DoorListener extends EventListener { /** * 門處理事件 * @param doorEvent 事件 */ void doorEvent(DoorEvent doorEvent); }
第三,定義事件監(jiān)聽器的實(shí)現(xiàn)類。
public class CloseDoorListener implements DoorListener{ @Override public void doorEvent(DoorEvent doorEvent) { if(doorEvent.getState() == -1){ System.out.println("門關(guān)上了"); } } } public class OpenDoorListener implements DoorListener{ @Override public void doorEvent(DoorEvent doorEvent) { if(doorEvent.getState() == 1){ System.out.println("門打開了"); } } }
這里實(shí)現(xiàn)了門的開和關(guān)兩個事件監(jiān)聽器類。
第四,定義事件源EventSource。
public class EventSource { //監(jiān)聽器列表,監(jiān)聽器的注冊則加入此列表 private Vector<DoorListener> listenerList = new Vector<>(); //注冊監(jiān)聽器 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("關(guān)門事件", -1)); eventSource.notifyListenerEvents(new DoorEvent("開門時間", 1)); } }
執(zhí)行測試類,控制臺打?。?/p>
門關(guān)上了
門打開了
事件成功觸發(fā)。
Spring中的事件機(jī)制
在了解了觀察者模式和Java的事件機(jī)制之后,再來看看Spring中的事件機(jī)制。在Spring容器中,通過ApplicationEvent
和ApplicationListener
接口來實(shí)現(xiàn)事件監(jiān)聽機(jī)制。每次Event事件被發(fā)布到Spring容器中,都會通知對應(yīng)的Listener。默認(rèn)情況下,Spring的事件監(jiān)聽機(jī)制是同步的。
Spring的事件監(jiān)聽由三部分組成:
- 事件(ApplicationEvent): 該類繼承自JDK中的EventObject,負(fù)責(zé)對應(yīng)相應(yīng)的監(jiān)聽器,事件源發(fā)生某事件是特定事件監(jiān)聽器被觸發(fā)的原因;
- 監(jiān)聽器(ApplicationListener):該類繼承自JDK中的EventListener,對應(yīng)于觀察者模式中的觀察者。監(jiān)聽器監(jiān)聽特定事件,并在內(nèi)部定義了事件發(fā)生后的響應(yīng)邏輯;
- 事件發(fā)布器(ApplicationEventPublisher):對應(yīng)于觀察者模式中的被觀察者/主題,負(fù)責(zé)通知觀察者,對外提供發(fā)布事件和增刪事件監(jiān)聽器的接口,維護(hù)事件和事件監(jiān)聽器之間的映射關(guān)系,并在事件發(fā)生時負(fù)責(zé)通知相關(guān)監(jiān)聽器。
通過上面的分析可以看出Spring的事件機(jī)制不僅是觀察者模式的一種實(shí)現(xiàn),也實(shí)現(xiàn)了JDK提供的事件接口。同時,除了發(fā)布者和監(jiān)聽者之外,還存在一個EventMulticaster的角色,負(fù)責(zé)把事件轉(zhuǎn)發(fā)給監(jiān)聽者。
Spring事件機(jī)制的工作流程如下:
在上述流程中,發(fā)布者調(diào)用applicationEventPublisher.publishEvent(msg),將事件發(fā)送給EventMultiCaster。EventMultiCaster注冊著所有的Listener,它會根據(jù)事件類型決定轉(zhuǎn)發(fā)給那個Listener。
在Spring中提供了一些標(biāo)準(zhǔn)的事件,比如:ContextRefreshEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent等。
關(guān)于Spring事件機(jī)制的具體實(shí)現(xiàn)和這些標(biāo)準(zhǔn)事件的作用,大家可以通過閱讀源碼來學(xué)習(xí),這里不再詳細(xì)展開。
下面來看看Spring事件機(jī)制涉及到的幾個角色的源碼及后續(xù)基于它們的實(shí)踐。
第一,事件(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; } }
事件可類比觀察者中的被觀察者實(shí)現(xiàn)類的角色,繼承自JDK的EventObject。上述Spring中的標(biāo)準(zhǔn)事件都是直接或間接繼承自該類。
第二,事件發(fā)布器(ApplicationEventPublisher)。
@FunctionalInterface public interface ApplicationEventPublisher { default void publishEvent(ApplicationEvent event) { publishEvent((Object) event); } void publishEvent(Object event); }
通過實(shí)現(xiàn)ApplicationEventPublisher接口,并重寫publishEvent()方法,可以自定義事件發(fā)布的邏輯。ApplicationContext繼承了ApplicationEventPublisher接口。因此,我們可以通過實(shí)現(xiàn)ApplicationContextAware接口,注入ApplicationContext,然后通過ApplicationContext的publishEvent()方法來實(shí)現(xiàn)事件發(fā)布功能。
ApplicationContext容器本身僅僅是對外提供了事件發(fā)布的接口publishEvent(),真正的工作委托給了具體容器內(nèi)部的ApplicationEventMulticaster對象。而ApplicationEventMulticaster對象可類比觀察者模式中的抽象被觀察者角色,負(fù)責(zé)持有所有觀察者集合的引用、動態(tài)添加、移除觀察者角色。
第三,事件監(jiān)聽器(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); }
事件監(jiān)聽器(ApplicationListener)對應(yīng)于觀察者模式中的具體觀察者角色。當(dāng)事件發(fā)布之后,就會執(zhí)行事件監(jiān)聽器的邏輯。通過實(shí)現(xiàn)ApplicationListener接口,并重寫onApplicationEvent()方法,就可以監(jiān)聽到事件發(fā)布器發(fā)布的事件。
Spring事件監(jiān)聽案例
下面以具體的案例代碼來說明如何自定義實(shí)現(xiàn)Spring事件監(jiān)聽。
第一,自定義定義事件對象,集成自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事件監(jiān)聽器。
@Component public class MyApplicationListener implements ApplicationListener<MyEvent> { @Override public void onApplicationEvent(MyEvent event) { // 監(jiān)聽到具體事件,處理對應(yīng)具體邏輯 System.out.println("event.getContext() = " + event.getContext()); } }
除了上述基于實(shí)現(xiàn)ApplicationListener接口的方式外,還可以使用 @EventListener注解來實(shí)現(xiàn),實(shí)現(xiàn)示例如下:
@Component public class MyApplicationListener{ // 通過注解實(shí)現(xiàn)監(jiān)聽器 @EventListener public void handleMyEvent(MyEvent event){ // 監(jiān)聽到具體事件,處理對應(yīng)具體邏輯 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!")); } }
執(zhí)行單元測試,可看到控制臺打印對應(yīng)的事件信息。
通過上述方式我們已經(jīng)成功實(shí)現(xiàn)了基于Spring的事件監(jiān)聽機(jī)制,但這其中還有一個問題:同步處理。默認(rèn)情況下,上述事件是基于同步處理的,如果其中一個監(jiān)聽器阻塞,那么整個線程將處于等待狀態(tài)。
那么,如何使用異步方式處理監(jiān)聽事件呢?只需兩步即可。
第一步,在監(jiān)聽器類或方法上添加@Async
注解,例如:
@Component @Async public class MyApplicationListener implements ApplicationListener<MyEvent> { @Override public void onApplicationEvent(MyEvent event) { // 監(jiān)聽到具體事件,處理對應(yīng)具體邏輯 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); } }
此時,就可以實(shí)現(xiàn)異步監(jiān)聽功能了。當(dāng)然,@Async
注解也可以指定我們已經(jīng)配置好的線程池來處理異步請求,關(guān)于線程數(shù)的初始化這里就不再演示了。
小結(jié)
本篇文章帶大家從觀察者模式、Java事件機(jī)制延伸到Spring的事件監(jiān)聽機(jī)制,將三者融合在一起來講解。通過這個案例,其實(shí)我們能夠體會到一些經(jīng)驗(yàn)性的知識,比如看似復(fù)雜的Spring事件監(jiān)聽機(jī)制實(shí)現(xiàn)只不過是觀察者模式的一種實(shí)現(xiàn),而其中又集成了Java的事件機(jī)制。這也就是所謂的融會貫通。
我們?nèi)绻麊渭兊膶W(xué)習(xí)某一個設(shè)計模式,可能只會運(yùn)用和識別它的簡單實(shí)現(xiàn),而實(shí)踐中往往會對設(shè)計模式進(jìn)行變種,甚至融合多種設(shè)計模式的優(yōu)點(diǎn)于一體,這便是活學(xué)活用。希望通過這邊文章你能夠更加深入的理解上述三者。
以上就是Spring事件監(jiān)聽機(jī)制觀察者模式詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring事件監(jiān)聽觀察者模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于文件上傳MultipartBody的使用方法
這篇文章主要介紹了關(guān)于文件上傳MultipartBody的使用方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06SpringBoot實(shí)現(xiàn)AOP切面的三種方式
Spring,SpringBoot框架憑借多種高效機(jī)制,顯著增強(qiáng)了代碼的功能性,并實(shí)現(xiàn)了切面編程(AOP)的精髓,其核心亮點(diǎn)之一,是運(yùn)用動態(tài)代理技術(shù),無需觸動源代碼即可在Bean的運(yùn)行時為其動態(tài)織入額外功能,本文給大家介紹了SpringBoot通過3種方式實(shí)現(xiàn)AOP切面,需要的朋友可以參考下2024-08-08Spring Boot集成Java DSL的實(shí)現(xiàn)代碼
這篇文章主要介紹了Spring Boot集成Java DSL的實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01Spring框架接入單機(jī)Redis兩種實(shí)現(xiàn)方式解析
這篇文章主要介紹了Spring框架接入單機(jī)Redis兩種實(shí)現(xiàn)方式解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09Spring中property-placeholder的使用與解析詳解
本篇文章主要介紹了Spring中property-placeholder的使用與解析詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05IDEA 錯誤 No main class specified的問題
這篇文章主要介紹了IDEA 錯誤 No main class specified的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04