Spring Event事件通知機(jī)制 源碼學(xué)習(xí)

筆記簡(jiǎn)述
本學(xué)習(xí)筆記主要是介紹Spring中的事件通知是如何實(shí)現(xiàn)的,同步和異步事件通知的用法和實(shí)現(xiàn)細(xì)節(jié)以及Spring提供的常見的Event,如果實(shí)際開發(fā)中需要根據(jù)事件推送完成相應(yīng)的功能,該如何選擇Event
Spring更多可查看Spring 源碼學(xué)習(xí)

目錄

Spring Event事件通知機(jī)制 源碼學(xué)習(xí)
1、監(jiān)聽者模式
2、DEMO(同步)
3、Spring實(shí)現(xiàn)細(xì)節(jié)
4、Spring Event
4.1 ContextRefreshedEvent
4.2 ServletRequestHandledEvent
5、異步Pushlish以及DEMO

1、監(jiān)聽者模式

學(xué)習(xí)spring的事件通知機(jī)制肯定要先了解監(jiān)聽者模式(監(jiān)聽者模式和觀察者模式有什么區(qū)別?

監(jiān)聽者模式包含了一個(gè)監(jiān)聽者Listener與之對(duì)應(yīng)的事件Event,還有一個(gè)事件發(fā)布者EventPublish,過程就是EventPublish發(fā)布一個(gè)事件,被監(jiān)聽者捕獲到,然后執(zhí)行事件相應(yīng)的方法

觀察者模式是一對(duì)多的模式,一個(gè)被觀察者Observable和多個(gè)觀察者Observer,被觀察者中存儲(chǔ)了所有的觀察者對(duì)象,當(dāng)被觀察者接收到一個(gè)外界的消息,就會(huì)遍歷廣播推算消息給所有的觀察者
例如日常生活中的訂閱報(bào)紙,報(bào)紙老板A,現(xiàn)在小明和老板打招呼說我要訂報(bào)紙(這個(gè)過程就相當(dāng)于觀察者的注冊(cè)),老板A就會(huì)拿出自己的小本本記下小明,下次小王、小芳也是類似的操作,現(xiàn)在老板A就有了三個(gè)觀察者了,然后老板會(huì)自動(dòng)的把報(bào)紙送到三位的家里,突然有一天小明說不想訂報(bào)紙了,老板就在自己的小本本上劃掉小明的名字(觀察者的取消注冊(cè)),等到送報(bào)紙的時(shí)候就不再送到小明家里。

2、DEMO(同步)

事件定義

public class EventDemo extends ApplicationEvent {
    private String message;
    public EventDemo(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

事件監(jiān)聽者

@Component
public class EventDemoListern implements ApplicationListener<EventDemo> {

    @Override
    public void onApplicationEvent(EventDemo event) {
        System.out.println("receiver " + event.getMessage());
    }
}

事件發(fā)布

@Component
public class EventDemoPublish {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void publish(String message){
        EventDemo demo = new EventDemo(this, message);
        applicationEventPublisher.publishEvent(demo);
    }

}

最后獲取到EventDemoPublish這個(gè)bean,直接publish就可以完成操作了。

3、Spring實(shí)現(xiàn)細(xì)節(jié)

在spring中由于類的細(xì)節(jié)太多,參數(shù)方法也非常的多,不太建議通篇的一個(gè)一個(gè)看,優(yōu)先關(guān)注我們關(guān)注的點(diǎn),然后打斷點(diǎn)的方式具體了解需要的參數(shù)和必備的方法等

AbstractApplicationContext類就有publishEvent()方法,先分析下。

protected void publishEvent(Object event, ResolvableType eventType) {
    Assert.notNull(event, "Event must not be null");
    if (logger.isTraceEnabled()) {
        logger.trace("Publishing event in " + getDisplayName() + ": " + event);
    }

    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
           // 如果是ApplicationEvent對(duì)象
        applicationEvent = (ApplicationEvent) event;
    }
    else {
        applicationEvent = new PayloadApplicationEvent<Object>(this, event);
        if (eventType == null) {
            eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
        }
        // 否則就包裝為PayloadApplicationEvent時(shí)間,并獲取對(duì)應(yīng)的事件類型
    }

    if (this.earlyApplicationEvents != null) {
           //初始化時(shí)候的事件容器,默認(rèn)為null的
        this.earlyApplicationEvents.add(applicationEvent);
    }
    else {
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        //  監(jiān)聽者容器?大概的就這個(gè)意思,推送消息給監(jiān)聽器
    }

    // 如果當(dāng)前命名空間還有父親節(jié)點(diǎn),也需要給父親推送該消息
    if (this.parent != null) {
        if (this.parent instanceof AbstractApplicationContext) {
            ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
        }
        else {
            this.parent.publishEvent(event);
        }
    }
}

ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
    if (this.applicationEventMulticaster == null) {
           // 意味著肯定需要提前初始化這個(gè)監(jiān)聽器容器
        throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
                "call 'refresh' before multicasting events via the context: " + this);
    }
    return this.applicationEventMulticaster;
}

通過分析,又回到了refresh這個(gè)核心函數(shù)中


image.png
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";

// 初始化監(jiān)聽器容器
protected void initApplicationEventMulticaster() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
           // 手動(dòng)實(shí)現(xiàn)了名稱為applicationEventMulticaster的bean
        this.applicationEventMulticaster =
                beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
        // 還必須得是ApplicationEventMulticaster類
        if (logger.isDebugEnabled()) {
            logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
        }
    }
    else {
           // 否則就默認(rèn)自定義一個(gè)名稱為SimpleApplicationEventMulticaster的監(jiān)聽器容器
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
                    APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
                    "': using default [" + this.applicationEventMulticaster + "]");
        }
    }
}

private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<ApplicationListener<?>>();
// 默認(rèn)是一個(gè)空列表,而不是null

// 注冊(cè)監(jiān)聽器,并且加入到監(jiān)聽器容器中
protected void registerListeners() {
    // Register statically specified listeners first.
    for (ApplicationListener<?> listener : getApplicationListeners()) {
        getApplicationEventMulticaster().addApplicationListener(listener);
        // 把提前存儲(chǔ)好的監(jiān)聽器添加到監(jiān)聽器容器中
    }

    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    // 獲取類型是ApplicationListener的beanName集合,此處不會(huì)去實(shí)例化bean
    for (String listenerBeanName : listenerBeanNames) {
        getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    }
    
    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    // 初始化事件為null
    if (earlyEventsToProcess != null) {
        for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
            getApplicationEventMulticaster().multicastEvent(earlyEvent);
        }
    }
}

這樣就很清楚了,在spring進(jìn)行refresh的時(shí)候就完成了監(jiān)聽器容器和監(jiān)聽器的初始化工作(可以很方便的注冊(cè)自己需要的監(jiān)聽器或者自定義的監(jiān)聽器容器對(duì)象),只需要獲取到容器就可以直接publish事件了。

4、Spring Event

前面已經(jīng)說了監(jiān)聽器一般和具體的某一個(gè)事件綁定的(這點(diǎn)就和觀察者模式非常不一樣了),那么就來看看spring已經(jīng)幫我們實(shí)現(xiàn)了哪些Event,具體如下圖

image.png
  • ApplicationContextEvent(Context...的抽象類)
  • ContextClosedEvent 生命周期關(guān)閉
  • ContextRefreshedEvent refresh完成
  • ContextStartedEvent 生命周期啟動(dòng)
  • ContextStoppedEvent 生命周期停止
  • PayloadApplicationEvent
  • RequestHandledEvent
  • ServletRequestHandledEvent RequestHandledEvent的子類,Spring MVC 請(qǐng)求完成之后推送的事件

下面就ContextRefreshedEvent和ServletRequestHandledEvent具體分析下如何使用以及其實(shí)現(xiàn)的原理

4.1 ContextRefreshedEvent

image.png

在refresh結(jié)束之后推送的一個(gè)事件,這個(gè)時(shí)候大部分bean的實(shí)例化已經(jīng)完成了,并且傳遞了this這個(gè)數(shù)據(jù),那么如果有需要的話,可以在這個(gè)地方實(shí)現(xiàn)對(duì)Spring上下文數(shù)據(jù)的統(tǒng)計(jì)或者監(jiān)控,簡(jiǎn)直不能爽歪歪,但是個(gè)人開發(fā)目前還沒遇到具體的使用場(chǎng)景

4.2 ServletRequestHandledEvent

Spring MVC 中的事件,直接跳到DispatcherServlet類,后來到了FrameworkServlet 抽象類

image.png

如果可以推送事件的話,則利用web的上下文推送事件,其中包含了一個(gè)請(qǐng)求的基本信息,如果需要定制化統(tǒng)計(jì)整個(gè)HTTP請(qǐng)求的情況,完全可以通過這個(gè)事件推送實(shí)現(xiàn)

不過這里有一點(diǎn)需要注意到,還有個(gè)publishEvents字段,如果需要使用,需要設(shè)置為true,如下圖web.xml配置即可

image.png

5、異步Pushlish以及DEMO

異步推送采用的是多線程的方法,具體看SimpleApplicationEventMulticaster類,也就是上面說的監(jiān)聽器容器

public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
           // 獲取符合事件類型的監(jiān)聽器集合
        Executor executor = getTaskExecutor();
        if (executor != null) {
              // 如果線程池不為null,則提交一個(gè)新任務(wù)交由線程池運(yùn)行
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    invokeListener(listener, event);
                }
            });
        }
        else {
              // 否則就是同步執(zhí)行
            invokeListener(listener, event);
        }
    }
}

protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
          // 錯(cuò)誤處理器不為null
        try {
            listener.onApplicationEvent(event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
            // 當(dāng)推送消息出現(xiàn)異常,利用錯(cuò)誤處理器去處理該錯(cuò)誤
        }
    }
    else {
        try {
            listener.onApplicationEvent(event);
        }
        catch (ClassCastException ex) {
            String msg = ex.getMessage();
            if (msg == null || msg.startsWith(event.getClass().getName())) {
                // Possibly a lambda-defined listener which we could not resolve the generic event type for
                Log logger = LogFactory.getLog(getClass());
                if (logger.isDebugEnabled()) {
                    logger.debug("Non-matching event type for listener: " + listener, ex);
                }
            }
            else {
                throw ex;
            }
        }
    }
}
  • 注入一個(gè)線程池可實(shí)現(xiàn)異步處理
  • 使用errorHandler可以做到最后的事件處理的兜底方案

那么問題來了,如何注入一個(gè)線程池呢?往哪里注入呢?

只需要手動(dòng)實(shí)現(xiàn)applicationEventMulticaster的bean,并且利用Set注入的方法注入了一個(gè)線程池,線程池也需要實(shí)例化,就直接使用了spring自帶的簡(jiǎn)單異步任務(wù)線程池

image.png

從同步改成異步,代碼不需要修改,直接注入這么一個(gè)bean即可(當(dāng)然了XML配置和Config配置均可)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,860評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,025評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,421評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,642評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,177評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,970評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,157評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,410評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評(píng)論 1 289
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,896評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,157評(píng)論 2 375

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