ApplicationEvent&ApplicationListener

1、參考

Standard and Custom Events

2涨享、前言

公司項(xiàng)目中有個(gè)發(fā)送站內(nèi)信的功能狡相,是使用ApplicationEventApplicationListener實(shí)現(xiàn)的定铜,對(duì)于這一塊,之前未接觸過,在該項(xiàng)目中蔚万,其具體功能如下:

  • ApplicationContext發(fā)送ApplicationEvent類型的站內(nèi)信
  • ApplicationListener接收到站內(nèi)信信息驴剔,進(jìn)行入庫(kù)處理届巩,createTime直接寫死成了new Date()
  • 用戶界面讀取入庫(kù)后的數(shù)據(jù)旱易,根據(jù)createTime倒序呈現(xiàn)給用戶

產(chǎn)品經(jīng)理要求站內(nèi)信需要按發(fā)送的順序呈現(xiàn)給用戶,那問題來(lái)了:

ApplicationListener接收到Event信息時(shí)建丧,是否是有序的呢排龄?

3、Demo

基于spring boot創(chuàng)建一個(gè)簡(jiǎn)易的ApplicationEvent使用demo翎朱,基于該demo執(zhí)行并發(fā)測(cè)試橄维,初步驗(yàn)證是否為有序接收

3.1尺铣、CustomEvent

自定義Event,繼承ApplicationEvent争舞,并實(shí)現(xiàn)構(gòu)造方法

public class CustomEvent extends ApplicationEvent {
    /**
     * Create a new ApplicationEvent.
     *
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public CustomEvent(Object source) {
        super(source);
    }
}

3.2凛忿、CustomPublisher

Event事件發(fā)布者,此處定義publish方法作為發(fā)送入口竞川,提供給controller調(diào)用店溢,方便測(cè)試

@Component
public class CustomPublisher {

    @Autowired
    private ApplicationContext applicationContext;

    public void publish(CustomEvent customEvent) {
        applicationContext.publishEvent(customEvent);
    }
}

除了使用注解的方式,spring也提供了實(shí)現(xiàn)ApplicationEventPublisherAware接口的方式委乌,具體可戳參考鏈接

3.3床牧、CustomListener

Event事件監(jiān)聽器,接收Event事件并對(duì)其進(jìn)行處理遭贸,此處進(jìn)行簡(jiǎn)單的打印

@Component
public class CustomListener {
    @EventListener
    public void listen(CustomEvent customEvent) {
        System.out.println(customEvent.getSource());
    }
}

除了使用注解的方式戈咳,spring也提供了ApplicationListener接口的方式,具體可戳參考鏈接

3.4革砸、測(cè)試

@RestController
public class RestController {
    @Autowired
    private CustomPublisher customPublisher;
    int i = 0;

    @GetMapping(value = "/publish")
    public void publish() {
        CustomEvent customEvent = new CustomEvent(i++);
        customPublisher.publish(customEvent);
    }
}

使用postmanrunner功能除秀,創(chuàng)建如下runner糯累,進(jìn)行并發(fā)測(cè)試:

runner

結(jié)果

從結(jié)果來(lái)看算利,事件接收是有序的,查閱官網(wǎng)泳姐,可以看到這么一句話:

You can register as many event listeners as you wish, but note that, by default, event listeners receive events synchronously. This means that the publishEvent() method blocks until all listeners have finished processing the event.
大致意思是:event listeners默認(rèn)上同步接收events效拭,這意味著,publicEvent()方法將會(huì)阻塞直至所有的listeners處理完event為止胖秒。

4缎患、源碼

追溯源碼,查看ApplicationEvent的發(fā)布和接收功能是如何實(shí)現(xiàn)的阎肝,首先挤渔,程序入口位于ApplicationContext#publishEvent

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

方法內(nèi)調(diào)用了重載的publicEvent,而該重載publishEvent是一個(gè)接口方法风题,有以下實(shí)現(xiàn):

publishEvent實(shí)現(xiàn)

顯然判导,具體的實(shí)現(xiàn)類是AbstractApplicationContext#publishEvent

    @Override
    public void publishEvent(Object event) {
        publishEvent(event, null);
    }

繼續(xù)調(diào)用重載方法publishEvent(Object event, @Nullable ResolvableType eventType)

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

        // Decorate event as an ApplicationEvent if necessary
        ApplicationEvent applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        }
        else {
            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 {
            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);
            }
        }
    }

邏輯可以總結(jié)為以下三點(diǎn):

  • 將入?yún)?code>event包裝成applicationEvent
  • 交由多播器進(jìn)行消息發(fā)布(multicastEvent
  • 若父級(jí)上下文不為空,則父級(jí)上下文同樣需要進(jìn)行消息發(fā)布

顯然沛硅,核心方法在于multicastEvent

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

這里使用了線程池眼刃,調(diào)用了invokeListener

    protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                doInvokeListener(listener, event);
            }
            catch (Throwable err) {
                errorHandler.handleError(err);
            }
        }
        else {
            doInvokeListener(listener, event);
        }
    }

繼續(xù)調(diào)用doInvokeListener

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            listener.onApplicationEvent(event);
        }
        catch (ClassCastException ex) {
            // ……
        }
    }

onApplicationEvent,是ApplicationListener接口類中的方法摇肌,在3.3節(jié)中擂红,有提及自定義監(jiān)聽器可以繼承ApplicationListener接口并實(shí)現(xiàn)onApplicationEvent方法進(jìn)行監(jiān)聽,若是使用該種方法围小,則事件的發(fā)送和接收處理的源碼就已經(jīng)追溯完畢昵骤,不過本文中使用的是注解树碱,故需要繼續(xù)跟蹤下去,onApplicationEvent的實(shí)現(xiàn)類頗多:

onApplicationEvent實(shí)現(xiàn)類

通過debug驗(yàn)證涉茧,ApplicationListenerMethodAdapter才是注解類listener的具體實(shí)現(xiàn):

    public void onApplicationEvent(ApplicationEvent event) {
        processEvent(event);
    }

查看processEvent

    public void processEvent(ApplicationEvent event) {
        Object[] args = resolveArguments(event);
        if (shouldHandle(event, args)) {
            Object result = doInvoke(args);
            if (result != null) {
                handleResult(result);
            }
            else {
                logger.trace("No result object given - no result to handle");
            }
        }
    }

可以看到核心在于doInvoke

    protected Object doInvoke(Object... args) {
        Object bean = getTargetBean();
        ReflectionUtils.makeAccessible(this.method);
        try {
            return this.method.invoke(bean, args);
        }
                // ……
      }

這里使用了反射赴恨,調(diào)用method方法,那么伴栓,method是什么時(shí)候賦值伦连,具體值又是什么呢,對(duì)method進(jìn)行Find Usages钳垮,可以看到:

    public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
        this.beanName = beanName;
        this.method = BridgeMethodResolver.findBridgedMethod(method);
        // ……
    }

method是在ApplicationListenerMethodAdapter構(gòu)造方法中賦值的惑淳,debug,可以看到具體值為:

public void com.kungyu.rabbitmq.CustomListener.listen(com.kungyu.rabbitmq.CustomEvent)

也就是我們自定義的監(jiān)聽方法饺窿,那么歧焦,ApplicationListenerMethodAdapter構(gòu)造方法何時(shí)被調(diào)用,繼續(xù)Find Usages

public class DefaultEventListenerFactory implements EventListenerFactory, Ordered {

    // ……
    @Override
    public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
        return new ApplicationListenerMethodAdapter(beanName, type, method);
    }

}

對(duì)createApplicationListener進(jìn)行Find Usages

protected void processBean(
            final List<EventListenerFactory> factories, final String beanName, final Class<?> targetType) {
    // ……
    ApplicationListener<?> applicationListener =
                                    factory.createApplicationListener(beanName, targetType, methodToUse);
    // ……
}

對(duì)processBean進(jìn)行Find Usages肚医,可以看到是在afterSingletonsInstantiated進(jìn)行調(diào)用的绢馍,而afterSingletonsInstantiated是類實(shí)例化相關(guān)的內(nèi)容,此處不予展開

5肠套、總結(jié)

從測(cè)試結(jié)果來(lái)看舰涌,ApplicationListener是有序接收消息的,從源碼來(lái)看你稚,其實(shí)現(xiàn)方式也不算過于復(fù)雜瓷耙,是對(duì)知識(shí)點(diǎn)的一個(gè)很好的補(bǔ)充。另外刁赖,在中間件橫行的時(shí)代搁痛,業(yè)務(wù)角度上貌似這一功能沒怎么被使用,出于解耦的目的宇弛,應(yīng)該都會(huì)用MQ實(shí)現(xiàn)該功能了鸡典。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市枪芒,隨后出現(xiàn)的幾起案子彻况,更是在濱河造成了極大的恐慌,老刑警劉巖病苗,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疗垛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡硫朦,警方通過查閱死者的電腦和手機(jī)贷腕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人泽裳,你說(shuō)我怎么就攤上這事瞒斩。” “怎么了涮总?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵胸囱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我瀑梗,道長(zhǎng)烹笔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任抛丽,我火速辦了婚禮谤职,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘亿鲜。我一直安慰自己允蜈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布蒿柳。 她就那樣靜靜地躺著饶套,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垒探。 梳的紋絲不亂的頭發(fā)上妓蛮,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音叛复,去河邊找鬼仔引。 笑死扔仓,一個(gè)胖子當(dāng)著我的面吹牛褐奥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翘簇,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼撬码,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了版保?” 一聲冷哼從身側(cè)響起呜笑,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎彻犁,沒想到半個(gè)月后叫胁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汞幢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年驼鹅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡输钩,死狀恐怖豺型,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情买乃,我是刑警寧澤姻氨,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站剪验,受9級(jí)特大地震影響肴焊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜功戚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一抖韩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疫铜,春花似錦茂浮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至谓厘,卻和暖如春幌羞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背竟稳。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工属桦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人他爸。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓聂宾,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親诊笤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子系谐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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