Spring中的事件發(fā)布機(jī)制原理解析
Spring事件發(fā)布機(jī)制原理
在 IoC 容器啟動(dòng)流程中有一個(gè) finishRefresh 方法,具體實(shí)現(xiàn)如下:
protected void finishRefresh() { clearResourceCaches(); initLifecycleProcessor(); getLifecycleProcessor().onRefresh(); // 向所有監(jiān)聽(tīng) ContextRefreshedEvent 事件的監(jiān)聽(tīng)者發(fā)布事件 publishEvent(new ContextRefreshedEvent(this)); LiveBeansView.registerApplicationContext(this); }
這里我們只關(guān)注 publishEvent 方法,這個(gè)方法用于發(fā)布 IoC 刷新完成事件,事件時(shí)如何發(fā)布的呢?下面我們一起來(lái)看一下。
一、原理分析
@Override public void publishEvent(ApplicationEvent event) { publishEvent(event, null); } 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; // 根據(jù)事件類(lèi)型進(jìn)行包裝 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 { // 獲取 ApplicationEventMulticaster,將事件廣播出去 getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); } // Publish event via parent context as well... // 判斷是否存在父容器,如果存在則將事件也發(fā)布到父容器的監(jiān)聽(tīng)者 if (this.parent != null) { if (this.parent instanceof AbstractApplicationContext) { ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); } else { this.parent.publishEvent(event); } } }
通過(guò)上面方法可以看出 Spring 的事件是通過(guò) ApplicationEventMulticaster 廣播出去的,這個(gè) ApplicationEventMulticaster 在 IoC 啟動(dòng)流程 initApplicationEventMulticaster 方法中初始化。如果該容器還存在父容器,那也會(huì)以同樣的形式將事件發(fā)布給父容器的監(jiān)聽(tīng)者。
@Override public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { // 解析事件類(lèi)型 ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); // 根據(jù)事件與事件類(lèi)型獲取所有監(jiān)聽(tīng)者 for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { // 獲取異步執(zhí)行器 Executor executor = getTaskExecutor(); if (executor != null) { // 如果執(zhí)行器部位 null,則異步執(zhí)行將事件發(fā)布給每一個(gè)監(jiān)聽(tīng)者 executor.execute(() -> invokeListener(listener, event)); } else { // 同步發(fā)布事件 invokeListener(listener, event); } } }
發(fā)布事件前會(huì)先獲取所有已注冊(cè)的監(jiān)聽(tīng)器,而監(jiān)聽(tīng)器早已在 IoC 啟動(dòng)流程的 registerListeners 方法中注冊(cè)。獲取到所有事件監(jiān)聽(tīng)器之后,就可以進(jìn)行事件發(fā)布了。發(fā)布的時(shí)候分為異步執(zhí)行與順序執(zhí)行,默認(rèn)情況下 executor 是沒(méi)有初始化的,因此是順序執(zhí)行。
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { // 獲取錯(cuò)誤處理機(jī)制 ErrorHandler errorHandler = getErrorHandler(); if (errorHandler != null) { try { // 事件發(fā)布 doInvokeListener(listener, event); } catch (Throwable err) { errorHandler.handleError(err); } } else { doInvokeListener(listener, event); } } private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { try { // 執(zhí)行監(jiān)聽(tīng)器的 onApplicationEvent 方法 listener.onApplicationEvent(event); } catch (ClassCastException ex) { String msg = ex.getMessage(); if (msg == null || matchesClassCastMessage(msg, event.getClass())) { // Possibly a lambda-defined listener which we could not resolve the generic event type for // -> let's suppress the exception and just log a debug message. Log logger = LogFactory.getLog(getClass()); if (logger.isTraceEnabled()) { logger.trace("Non-matching event type for listener: " + listener, ex); } } else { throw ex; } } }
到這里 Spring 的事件通知機(jī)制流程就結(jié)束了,總的來(lái)說(shuō)還是比較好理解的。
二、事件通知 demo
了解了事件通知機(jī)制的基本原理后,下面我們來(lái)寫(xiě)個(gè) demo 體驗(yàn)一下監(jiān)聽(tīng)器是如何使用的。
// 定義一個(gè) Event public class EventDemo extends ApplicationEvent { private static final long serialVersionUID = -8363050754445002832L; private String message; public EventDemo(Object source, String message) { super(source); this.message = message; } public String getMessage() { return message; } } // 定義一個(gè)監(jiān)聽(tīng)器1 public class EventDemo1Listener implements ApplicationListener<EventDemo> { public void onApplicationEvent(EventDemo event) { System.out.println(this + " receiver " + event.getMessage()); } } // 定義一個(gè)監(jiān)聽(tīng)器2 public class EventDemo2Listener implements ApplicationListener<EventDemo> { public void onApplicationEvent(EventDemo event) { System.out.println(this + " receiver " + event.getMessage()); } } // 定義一個(gè)事件發(fā)布者 public class EventDemoPublish { public void publish(ApplicationEventPublisher applicationEventPublisher, String message) { EventDemo eventDemo = new EventDemo(this, message); applicationEventPublisher.publishEvent(eventDemo); } }
在 XML 中配置 bean:
<bean id="eventDemoPublish" class="com.jas.mess.event.EventDemoPublish"/> <bean id="eventDemo1Listener" class="com.jas.mess.event.EventDemo1Listener"/> <bean id="eventDemo2Listener" class="com.jas.mess.event.EventDemo2Listener"/>
編寫(xiě)測(cè)試類(lèi):
@Test public void eventTest() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation); applicationContext.getBean("eventDemoPublish", EventDemoPublish.class).publish(applicationContext, "hello world"); }
控制臺(tái)輸出:
不知道你有沒(méi)有注意到,在發(fā)布事件的時(shí)候我們傳的發(fā)布者是 applicationContext,applicationContext 本身繼承自 ApplicationEventPublisher 接口,因此它本身也是一個(gè)事件發(fā)布者。
到此這篇關(guān)于Spring中的事件發(fā)布機(jī)制原理解析的文章就介紹到這了,更多相關(guān)Spring事件發(fā)布機(jī)制原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring的事件發(fā)布與監(jiān)聽(tīng)方式案例講解
- 詳解SpringBoot實(shí)現(xiàn)ApplicationEvent事件的監(jiān)聽(tīng)與發(fā)布
- spring event 事件異步處理方式(發(fā)布,監(jiān)聽(tīng),異步處理)
- SpringBoot事件發(fā)布與監(jiān)聽(tīng)超詳細(xì)講解
- Spring事件發(fā)布監(jiān)聽(tīng),順序監(jiān)聽(tīng),異步監(jiān)聽(tīng)方式
- SpringBoot事件發(fā)布和監(jiān)聽(tīng)詳解
- 詳解Spring事件發(fā)布與監(jiān)聽(tīng)機(jī)制
相關(guān)文章
開(kāi)發(fā)者在Idea 中常見(jiàn)的配置,你都了解嗎
idea這款java開(kāi)發(fā)工具真是好用無(wú)比,不僅好用而且界面也很好看,有黑白主題,功能強(qiáng)大配置簡(jiǎn)單,好了不多說(shuō)了,今天給大家羅列下Idea 中常見(jiàn)的配置,喜歡的朋友一起看看吧2021-06-06Java基于分治法實(shí)現(xiàn)的快速排序算法示例
這篇文章主要介紹了Java基于分治法實(shí)現(xiàn)的快速排序算法,結(jié)合實(shí)例形式分析了java基于分治法的快速排序相關(guān)實(shí)現(xiàn)技巧,代碼中備有較為詳細(xì)的注釋說(shuō)明便于理解,需要的朋友可以參考下2017-12-12詳解SpringBoot 快速整合MyBatis(去XML化)
本篇文章主要介紹了詳解SpringBoot 快速整合MyBatis(去XML化),非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-10-10Spring?BeanDefinition父子關(guān)系示例解析
這篇文章主要為大家介紹了Spring?BeanDefinition父子關(guān)系示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08java調(diào)用微信現(xiàn)金紅包接口的心得與體會(huì)總結(jié)
這篇文章主要介紹了java調(diào)用微信現(xiàn)金紅包接口的心得與體會(huì)總結(jié),有需要的朋友可以了解一下。2016-11-11java8 Stream list to Map key 重復(fù) value合并到Collectio的操作
這篇文章主要介紹了java8 Stream list to Map key 重復(fù) value合并到Collectio的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Java concurrency集合之ConcurrentSkipListSet_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Java concurrency集合之ConcurrentSkipListSet的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06