[Spring]詳解Spring中的事件監(jiān)聽器模式

1. 事件監(jiān)聽器模式的重要因素

  • Event Object: 事件,事件源會將事件進(jìn)行發(fā)布恶阴。Spring中的事件對象為ApplicationEvent.
  • Event Listener: 事件監(jiān)聽器刹勃,負(fù)責(zé)處理訂閱的事件. Spring中對應(yīng)的事件監(jiān)聽器接口為ApplicationListener.
  • Event Source: 事件源钠导,負(fù)責(zé)發(fā)布事件并通知事件監(jiān)聽器芜抒。Spring中對應(yīng)的事件源接口為ApplicationEventPublisher.

關(guān)于事件監(jiān)聽器模式锅纺,如果你不夠熟悉,可以在我的上篇博客得到解答->點(diǎn)我前往

2. ApplicationEvent

2.1 接口清單

值得一提的是铣耘,Spring中的ApplicationEvent是內(nèi)置事件源的洽沟,這意味著在監(jiān)聽器中可以獲取事件源,而Spring中的事件源通常為容器本身.
同時蜗细,事件發(fā)生的同時裆操,會記錄當(dāng)前系統(tǒng)時間戳

/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context;

import java.util.EventObject;

/**
 * Class to be extended by all application events. Abstract as it
 * doesn't make sense for generic events to be published directly.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
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 ApplicationEvent.
     * @param source the object on which the event initially occurred (never {@code null})<br>
     * 創(chuàng)建一個應(yīng)用事件,其中source為事件源炉媒,并且不能為空.
     */
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }


    /**
     * Return the system time in milliseconds when the event happened.<br>
     * 返回當(dāng)前事件發(fā)生時的系統(tǒng)時間
     */
    public final long getTimestamp() {
        return this.timestamp;
    }

}

2.2 UML

UML

看類圖可以知道踪区,ApplicationEvent繼承自JDK的EventObject,同時還有一個抽象子類ApplicationContextEvent,為什么要再抽象出這個子類呢吊骤,因?yàn)镺bject本身的含義太過廣泛缎岗,Spring為了定義容器事件,強(qiáng)制約束source對象本身的類型為ApplicationContext.
最后從這個抽象類白粉,衍生了一系列單一職責(zé)的事件传泊。分別對應(yīng)容器的關(guān)閉、刷新蜗元、啟動或渤、停止等階段.

2.3 PayloadApplicationEvent

Spring 4.2 后推出一個一個基于泛型對事件進(jìn)行包裝的類,在此之前奕扣,發(fā)布的Event都必須繼承自ApplicationEvent.加入Payload機(jī)制后薪鹦,在發(fā)布事件的時候,可以傳輸任意的Object,Spring內(nèi)部都會用PayloadApplicationEvent對事件進(jìn)行包裝.

具體的改進(jìn)在org.springframework.context.ApplicationEventPublisher#publishEvent(java.lang.Object)可以做更加深入的了解惯豆。

default

3. ApplicationListener

3.1 接口清單

函數(shù)式接口池磁,同時監(jiān)聽的事件需為繼承自ApplicationEvent的類,如果Spring為4.2后的楷兽,可以不受這個限制地熄,因?yàn)閮?nèi)部使用PayloadApplicationEvent進(jìn)行了包裝,在事件源發(fā)布事件時芯杀,會觸發(fā)onApplicationEvent通知監(jiān)聽器端考。

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

    /**
     * Handle an application event.
     * @param event the event to respond to
     * 處理應(yīng)用事件
     */
    void onApplicationEvent(E event);

}

3.2 UML

UML
  • SmartApplicationListener: 支持事件消費(fèi)排序與事件類型匹配,Spring3.0開始支持揭厚,需要實(shí)現(xiàn)的方法為boolean supportsEventType(Class<? extends ApplicationEvent> eventType);,注意這里推斷的對象是Class.
  • GenericApplicationListener: 支持事件消費(fèi)排序與事件類型匹配却特,Spring4.2開始支持,需要實(shí)現(xiàn)的方法為boolean supportsEventType(ResolvableType eventType);,注意這里推斷的對象是ResolvableType.

3.3 注解支持-@EventListener

Spring4.2后筛圆,對事件監(jiān)聽器增加了注解的支持裂明,無需實(shí)現(xiàn)接口,只需要通過@EventListener來直接在需要響應(yīng)的方法上標(biāo)注即可太援,配合Async@Order注解還可以支持異步與消費(fèi)順序聲明.

注意闽晦,注解標(biāo)注的方法上扳碍,最好聲明為void,如果返回類型為數(shù)組或者集合,Spring會將每個元素作為新的事件進(jìn)行發(fā)布仙蛉。

4.ApplicationEventPublisher&ApplicationEventMulticaster

4.1 ApplicationEventPublisher

4.1.1 接口清單

通常在SpringIOC中笋敞,容器本身會作為ApplicationEventPublisher去進(jìn)行事件的發(fā)布.
同時,開發(fā)者還可以通過Aware接口訪問到ApplicationEventPublisher實(shí)例.來發(fā)布自定義事件.

@FunctionalInterface
public interface ApplicationEventPublisher {
    // 通知所有與此應(yīng)用程序注冊的匹配偵聽器一個應(yīng)用程序事件捅儒。
    // 事件可以是框架事件(例如ContextRefreshedEvent)或特定于應(yīng)用程序的事件液样。
    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }

    // 通知所有與此應(yīng)用程序注冊的匹配偵聽器事件。
    // 如果指定的事件不是ApplicationEvent巧还,則將其包裝在PayloadApplicationEvent中。
    void publishEvent(Object event);

}
4.1.2 UML
UML

這里再次驗(yàn)證坊秸,容器即為IOC的ApplicationEventPublisher.

4.2 ApplicationEventMulticaster

Spring中的publisher只提供了發(fā)布事件的接口麸祷,然而一個事件監(jiān)聽器模式少不了注冊監(jiān)聽器這件事情,ApplicationEventMulticaster就是為了解決這件事而產(chǎn)生的褒搔。

4.2.1 接口清單
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);

}

從接口清單我們可以看到,ApplicationEventMulticaster支持添加監(jiān)聽器阶牍、移除監(jiān)聽器、發(fā)布事件星瘾。

4.2.2 UML
UML
  • AbstractApplicationEventMulticaster: 內(nèi)部持有一個defaultRetriever成員變量走孽,該變量為ListenerRetriever,其內(nèi)部使用Set存儲ApplicationListener琳状。 AbstractApplicationEventMulticaster添加監(jiān)聽器的操作為this.defaultRetriever.applicationListeners.add(listener);.有興趣的讀者可以自行擴(kuò)展閱讀.
  • SimpleApplicationEventMulticaster: 繼承自AbstractApplicationEventMulticaster磕瓷,內(nèi)置private Executor taskExecutor;-任務(wù)執(zhí)行器。默認(rèn)情況下念逞,監(jiān)聽器都以同步的方式進(jìn)行困食,但是會由于個別監(jiān)聽器速度過慢,導(dǎo)致任務(wù)進(jìn)度阻塞翎承,因此該事件發(fā)布器也支持了以線程池來提交異步任務(wù)的方式消費(fèi)事件.
    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)) {
            // 如果線程池不為空,則使用線程池提交任務(wù).
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

4.3 為什么要同時定義ApplicationEventMulticaster和ApplicationEventPublisher?

org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)中硕盹,我們可以看到,容器內(nèi)部是怎么發(fā)送事件的.

    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");

        // Decorate event as an ApplicationEvent if necessary
        ApplicationEvent applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        }
        else {
            // 如果事件對象不是ApplicationEvent叨咖,則使用PayloadApplicationEvent進(jìn)行包裝
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
            }
        }

        // Multicast right now if possible - or lazily once the multicaster is initialized
        if (this.earlyApplicationEvents != null) {
            this.earlyApplicationEvents.add(applicationEvent);
        }
        else {
            // 重點(diǎn)瘩例,可以看到publishEvent其實(shí)是通過ApplicationEventMulticaster進(jìn)行真正的事件發(fā)布
            getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        }

        // Publish event via parent context as well...
        // 一些遞歸的操作
        if (this.parent != null) {
            if (this.parent instanceof AbstractApplicationContext) {
                ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
            }
            else {
                this.parent.publishEvent(event);
            }
        }
    }

簡單地說,就是ApplicationEventPublisher是一個簡單的事件發(fā)布接口甸各,只負(fù)責(zé)聲明入口垛贤,而ApplicationEventMulticaster負(fù)責(zé)處理真正的事件發(fā)布邏輯。這其實(shí)是一種委托的思想.你可以在Spring隨處發(fā)現(xiàn)這種現(xiàn)象痴晦,如Registry接口其實(shí)真正的執(zhí)行者為DefaultListableBeanFactory.

總結(jié)

  • Spring中的事件監(jiān)聽器模式擴(kuò)展自JDK中的事件監(jiān)聽器模型南吮。
  • 事件在早期版本必須繼承自ApplicationEvent,4.2后Spring引入了PayloadApplicationEvent進(jìn)行兼容,支持以O(shè)bject的形式發(fā)送事件.
  • 事件源在IOC中通常為容器本身.
  • 事件監(jiān)聽器支持實(shí)現(xiàn)接口和注解形式進(jìn)行實(shí)現(xiàn),同時可以使用@Order注解來指定消費(fèi)順序誊酌。
  • 避免在事件監(jiān)聽器中調(diào)用事件源的發(fā)布事件部凑,引起循環(huán)引用的問題露乏。
  • Spring以委托的思想,建立了事件發(fā)布者的接口視圖->ApplicationEventPublisher,其中真正的執(zhí)行者則為ApplicationEventMulticaster接口的實(shí)現(xiàn)類.
  • ApplicationEventMulticaster提供了異步的支持涂邀。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瘟仿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子比勉,更是在濱河造成了極大的恐慌劳较,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浩聋,死亡現(xiàn)場離奇詭異观蜗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)衣洁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門墓捻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人坊夫,你說我怎么就攤上這事砖第。” “怎么了环凿?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵梧兼,是天一觀的道長。 經(jīng)常有香客問我智听,道長羽杰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任瞭稼,我火速辦了婚禮忽洛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘环肘。我一直安慰自己欲虚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布悔雹。 她就那樣靜靜地躺著复哆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腌零。 梳的紋絲不亂的頭發(fā)上梯找,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機(jī)與錄音益涧,去河邊找鬼锈锤。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的久免。 我是一名探鬼主播浅辙,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼阎姥!你這毒婦竟也來了记舆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤呼巴,失蹤者是張志新(化名)和其女友劉穎泽腮,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衣赶,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诊赊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了屑埋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片豪筝。...
    茶點(diǎn)故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖摘能,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情敲街,我是刑警寧澤团搞,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站多艇,受9級特大地震影響逻恐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜峻黍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一复隆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧姆涩,春花似錦挽拂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宏赘,卻和暖如春绒北,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背察署。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工闷游, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓脐往,卻偏偏與公主長得像休吠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子钙勃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容