Spring5IOC容器解析——事件監(jiān)聽機制

一、事件驅(qū)動模型簡介

事件驅(qū)動模型板鬓,也即是我們通常說的觀察者【啃簦基于發(fā)布-訂閱模式的編程模型俭令。

概念

定義對象間的一種一對多的依賴關(guān)系,當(dāng)一個對象的狀態(tài)發(fā)生變化時部宿,所有依賴它的對象都得到通知并自動更新抄腔。

百度百科:
從事件角度說瓢湃,事件驅(qū)動程序的基本結(jié)構(gòu)是由一個事件收集器、一個事件發(fā)送器和一個事件處理器組成赫蛇。
事件收集器專門負(fù)責(zé)收集所有事件绵患,包括來自用戶的(如鼠標(biāo)、鍵盤事件等)悟耘、來自硬件的(如時鐘事件等)和來自軟件的(如操作系統(tǒng)落蝙、應(yīng)用程序本身等)。
事件發(fā)送器負(fù)責(zé)將收集器收集到的事件分發(fā)到目標(biāo)對象中暂幼。
事件處理器做具體的事件響應(yīng)工作筏勒。

從程序設(shè)計的角度來看,事件驅(qū)動模型的核心構(gòu)件通常包含以下幾個:

  • 事件源(Event Source):負(fù)責(zé)產(chǎn)生事件的對象粟誓。比如我們常見的按鈕奏寨,按鈕就是一個事件源,能夠產(chǎn)生“點擊”這個事件
  • 事件監(jiān)聽器(Event Listener):注冊在事件源上才能被調(diào)用鹰服,主要用于監(jiān)聽事件并進行事件處理或者轉(zhuǎn)發(fā)病瞳。
  • 事件對象(Event Object):或者稱為事件對象,是事件源和事件監(jiān)聽器之間的信息橋梁悲酷。是整個事件模型驅(qū)動的核心

下圖展示了事件套菜、事件源、監(jiān)聽器直接的關(guān)系:


1设易、 觀察者模式

原理解析

觀察者模式的UML圖如下:


具體類說明如下:

  • Observer觀察者:即為事件模型中的事件監(jiān)聽器逗柴,該接口定義一個方法update,即為事件處理方法顿肺。當(dāng)觀察到有事件產(chǎn)生時戏溺,該方法便會處理
  • Subject被觀察者:即事件模型中的事件源,負(fù)責(zé)產(chǎn)生事件屠尊,定義與觀察者建立關(guān)聯(lián)的方法(添加觀察者旷祸、刪除觀察者、通知觀察者)
  • ConreteObserver具體的觀察者實現(xiàn)類:實現(xiàn)Observer接口中的update方法讼昆,具體實例會被添加到被觀察者的觀察者隊列中(observers[List])
  • ConreteSubject具體的被觀察者實現(xiàn)類:實現(xiàn)Subject接口托享。定義觀察者隊列(observers[List]),并定義實現(xiàn)如何將觀察者對象添加到觀察者隊列中以及如何通所有知觀察者

在上述類圖中浸赫,具體的ConreteSubject被觀察者闰围,其中包含observers一個列表,保存所有觀察者對象既峡。doAction方法是需要通知觀察者對象的動作羡榴,當(dāng)該方法執(zhí)行后,會通知保存在observers中的所有觀察者运敢。

也就是在執(zhí)行方法ConreteSubject#doAction()時炕矮,需要調(diào)用ConreteSubject#notifyObservers()通知保存在observers中的所有觀察者么夫,讓其能夠做出響應(yīng)者冤。

Spring 中的事件監(jiān)聽機制

Spring 中的事件

Spring 中的事件通知機制就是觀察者模式的一種實現(xiàn)肤视。觀察者是 ApplicationListener,可以實現(xiàn)接口定義觀察者涉枫,也可以使用注解定義觀察者邢滑。觀察者感興趣的是某種狀態(tài)的變化,這種狀態(tài)變化使用 ApplicationEvent 來傳達愿汰,也就是事件對象困后。我們說的 Spring 中的事件,就是 ApplicationEvent衬廷。在事件中摇予,被觀察者可以認(rèn)為是發(fā)出事件的一方,只有在狀態(tài)變化時才發(fā)布事件吗跋。

當(dāng)有狀態(tài)發(fā)生變化時侧戴,發(fā)布者調(diào)用 ApplicationEventPublisher 的 publishEvent 方法發(fā)布一個事件,Spring 容器廣播事件給所有觀察者跌宛,調(diào)用觀察者的 onApplicationEvent 方法把事件對象傳遞給觀察者酗宋。調(diào)用 publishEvent 方法有兩種途徑,一種是實現(xiàn)接口由容器注入 ApplicationEventPublisher 對象然后調(diào)用其方法疆拘,另一種是直接調(diào)用容器的方法蜕猫,兩種方法發(fā)布事件沒有太大區(qū)別。

相關(guān)類型總結(jié)如下:

  • ApplicationListener:事件監(jiān)聽者哎迄,觀察者回右;
  • ApplicationEvent:Spring 事件,記錄事件源漱挚、時間和數(shù)據(jù)翔烁;
  • ApplicationEventPublisher:發(fā)布事件;
優(yōu)勢

使用事件可以解耦代碼棱烂,觀察者與被觀察者可以分開開發(fā)租漂,中間只有事件作為聯(lián)系,不用關(guān)心另一方如何實現(xiàn)颊糜。觀察者可以有多個哩治,所以對于同一個事件可以有多種不同的處理方式,不過要確保不依賴處理的順序衬鱼。使用事件后业筏,觀察者可以單獨開發(fā),對主流程沒有任何影響鸟赫,可以簡化主流程的開發(fā)蒜胖。

事件可以用于各種場景的消息通知消别,比如系統(tǒng)收到停機指令后,可以發(fā)出停機事件台谢,這樣相關(guān)的子系統(tǒng)就可以進行停機前的各種處理寻狂。那么 Spring 中的事件是如何實現(xiàn)的呢?

Spring事件驅(qū)動模型的三大組成部分

  • ApplicationEvent:事件對象朋沮,Spring事件驅(qū)動模型中的對象源蛇券,繼承JDK EventObject,通過在發(fā)布事件時通過EventObject.source字段攜帶事件相關(guān)的數(shù)據(jù)樊拓。

  • ApplicationListener:應(yīng)用監(jiān)聽器纠亚,負(fù)責(zé)監(jiān)聽事件對象是否有發(fā)生變化,實現(xiàn)該接口并實現(xiàn)onApplicationEvent方法筋夏,完成事件發(fā)生變化時的邏輯處理

  • ApplicationEventPublisher:事件發(fā)布器蒂胞,定義了事件發(fā)布規(guī)范,只定義了接口条篷,具體的實現(xiàn)交由其他類中實現(xiàn)骗随。Spring提供了SimpleApplicationEventMulticaster實現(xiàn)了廣播事件發(fā)布器嚎花。

Spring 框架提供了四種容器事件细卧,我們可以直接觀察,包括:

  • ContextStartedEvent:ApplicationContext 啟動事件闺属。
  • ContextRefreshedEvent:ApplicationContext 更新事件稚瘾。
  • ContextStoppedEvent:ApplicationContext 停止事件牡昆。
  • ContextClosedEvent:ApplicationContext 關(guān)閉事件。

Spring 4.2 之前的版本摊欠,事件必須繼承 ApplicationEvent丢烘,從 Spring 4.2 版開始,框架提供了 PayloadApplicationEvent 用于包裝任意類型些椒,不強制事件對象繼承 ApplicationEvent播瞳。當(dāng)我們發(fā)送一個任意類型的事件對象時,框架內(nèi)部自動包裝為 PayloadApplicationEvent 事件對象免糕。

事件監(jiān)聽者

事件監(jiān)聽者 ApplicationListener 繼承自 JDK 的 EventListener 赢乓,JDK 要求所有監(jiān)聽者繼承它。監(jiān)聽者只有一個方法 onApplicationEvent石窑,用來處理事件 ApplicationEvent牌芋。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);

}

在容器啟動的時候檢測應(yīng)用中的監(jiān)聽者并把用戶實現(xiàn)的監(jiān)聽者注冊到 SimpleApplicationEventMulticaster 集合中。

Spring 也支持直接注解的形式進行事件監(jiān)聽松逊,使用注解 @EventListener 即可躺屁。使用注解時,方法可以返回任意類型经宏,如果返回值不為 void 則當(dāng)做一個新事件再次發(fā)布犀暑。

@Service
public class AnnoEventListener {
    @EventListener
    public void listen(MyEvent myEvent){
        System.out.println("receive " + myEvent.getSource());
    }
}

注解形式的監(jiān)聽者是通過 EventListenerMethodProcessor 注冊到容器中的驯击。該類定義了一個 Bean,在初始化完成后耐亏,調(diào)用它的后置回調(diào)方法 afterSingletonsInstantiated徊都,在方法中遍歷容器中所有的 bean,提取出 EventListener 注解修飾的方法并根據(jù)注解的參數(shù)創(chuàng)建 ApplicationListener 對象加入到 ApplicationContext 監(jiān)聽列表中苹熏。

發(fā)布事件

ApplicationEventPublisher 接口定義了事件發(fā)布方法碟贾,代碼如下:

@FunctionalInterface
public interface ApplicationEventPublisher {

    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }

    void publishEvent(Object event);
}

ApplicationContext 接口繼承了 ApplicationEventPublisher ,并在 AbstractApplicationContext 實現(xiàn)了具體代碼轨域,實際執(zhí)行是委托給 ApplicationEventMulticaster。

ApplicationEventMulticaster 接口定義了對監(jiān)聽者的操作杀餐,如增加監(jiān)聽者干发、移除監(jiān)聽者,以及發(fā)布事件的方法史翘⊥鞒ぃ框架提供了 ApplicationEventMulticaster 接口的默認(rèn)實現(xiàn)SimpleApplicationEventMulticaster,如果本地容器中沒有 ApplicationEventMulticaster 的實現(xiàn)就會使用這個默認(rèn)實現(xiàn)琼讽。

public interface ApplicationEventMulticaster {

    void addApplicationListener(ApplicationListener<?> listener);

    void addApplicationListenerBean(String listenerBeanName);

    void removeApplicationListener(ApplicationListener<?> listener);

    void removeApplicationListenerBean(String listenerBeanName);

    void removeAllListeners();

    void multicastEvent(ApplicationEvent event);

    void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}

在 SimpleApplicationEventMulticaster 中必峰,可以看到 multicastEvent 方法中遍歷了所有的 Listener,并依次調(diào)用 Listener 的 onApplicationEvent 方法發(fā)布事件钻蹬。Spring 還提供了異步發(fā)布事件的能力吼蚁,taskExecutor 不為 null 時即異步執(zhí)行。

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

    @Nullable
    private Executor taskExecutor;

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }
}

注意事項:
可以看到问欠,Spring 處理事件是同步的肝匆,監(jiān)聽者的執(zhí)行時間最好不要太長,以免影響主流程顺献。如果事件的處理一定要耗費比較長的時間旗国,請使用異步方式進行處理。

高級監(jiān)聽者

SmartApplicationListener

SmartApplicationListener 接口是 ApplicationListener 的子接口注整,還繼承了 Ordered 接口能曾。SmartApplicationListener 定義了兩個 support 方法用于判斷事件類型、來源類型是否和當(dāng)前監(jiān)聽者匹配肿轨,這樣監(jiān)聽者可以篩選自己感興趣的事件和來源寿冕。繼承 Ordered 接口后,該監(jiān)聽者具備了排序的功能萝招,可以按照 order 從小到大的順序給監(jiān)聽者確定一個優(yōu)先級蚂斤,從而確保執(zhí)行順序。

public interface Ordered {

    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

    int getOrder();
}


public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
    
    //指定支持哪些類型的事件
    //判斷事件的類型是否和當(dāng)前監(jiān)聽者匹配
    boolean supportsEventType(Class<? extends ApplicationEvent> eventType);

    //指定支持發(fā)生事件所在的類型
    //判斷事件源類型是否和當(dāng)前監(jiān)聽者匹配
    default boolean supportsSourceType(@Nullable Class<?> sourceType) {
        return true;
    }

    @Override
    default int getOrder() {
        return LOWEST_PRECEDENCE;
    }

}

GenericApplicationListener

GenericApplicationListener 接口是 ApplicationListener 的子接口槐沼,也繼承了 Ordered 接口曙蒸,同 SmartApplicationListener 一樣具有事件篩選能力和排序能力捌治。但篩選事件使用的是 ResolvableType 類型,而不是 ApplicationEvent 類型纽窟。

ResolvableType類型是Spring4之后添加進來的獲取泛型信息的工具肖油,通過ResolvableType可以獲取到傳入的泛型的各種信息。

public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

    //指定支持哪些類型的事件
    //判斷事件的類型是否和當(dāng)前監(jiān)聽者匹配
    boolean supportsEventType(ResolvableType eventType);

    //指定支持發(fā)生事件所在的類型
    //判斷事件源類型是否和當(dāng)前監(jiān)聽者匹配
    default boolean supportsSourceType(@Nullable Class<?> sourceType) {
        return true;
    }

    @Override
    default int getOrder() {
        return LOWEST_PRECEDENCE;
    }

}

參考:
https://blog.csdn.net/zrudong/article/details/78567473

https://zhuanlan.zhihu.com/p/40071652

https://blog.csdn.net/chenwiehuang/article/details/80641811

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末臂港,一起剝皮案震驚了整個濱河市森枪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌审孽,老刑警劉巖县袱,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異佑力,居然都是意外死亡式散,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門打颤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來暴拄,“玉大人,你說我怎么就攤上這事编饺」耘瘢” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵透且,是天一觀的道長撕蔼。 經(jīng)常有香客問我,道長石蔗,這世上最難降的妖魔是什么罕邀? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮养距,結(jié)果婚禮上诉探,老公的妹妹穿的比我還像新娘。我一直安慰自己棍厌,他們只是感情好肾胯,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耘纱,像睡著了一般敬肚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上束析,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天艳馒,我揣著相機與錄音,去河邊找鬼。 笑死弄慰,一個胖子當(dāng)著我的面吹牛第美,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播陆爽,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼什往,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了慌闭?” 一聲冷哼從身側(cè)響起别威,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驴剔,沒想到半個月后省古,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡仔拟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年衫樊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片利花。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖载佳,靈堂內(nèi)的尸體忽然破棺而出炒事,到底是詐尸還是另有隱情,我是刑警寧澤蔫慧,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布挠乳,位于F島的核電站,受9級特大地震影響姑躲,放射性物質(zhì)發(fā)生泄漏睡扬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一黍析、第九天 我趴在偏房一處隱蔽的房頂上張望卖怜。 院中可真熱鬧,春花似錦阐枣、人聲如沸马靠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甩鳄。三九已至,卻和暖如春额划,著一層夾襖步出監(jiān)牢的瞬間妙啃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工俊戳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留揖赴,地道東北人馆匿。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像储笑,于是被迫代替她去往敵國和親甜熔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344