Spring中的事件發(fā)布機制原理解析
Spring事件發(fā)布機制原理
在 IoC 容器啟動流程中有一個 finishRefresh 方法,具體實現(xiàn)如下:
protected void finishRefresh() {
clearResourceCaches();
initLifecycleProcessor();
getLifecycleProcessor().onRefresh();
// 向所有監(jiān)聽 ContextRefreshedEvent 事件的監(jiān)聽者發(fā)布事件
publishEvent(new ContextRefreshedEvent(this));
LiveBeansView.registerApplicationContext(this);
}
這里我們只關(guān)注 publishEvent 方法,這個方法用于發(fā)布 IoC 刷新完成事件,事件時如何發(fā)布的呢?下面我們一起來看一下。
一、原理分析
@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ù)事件類型進(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)聽者
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
} else {
this.parent.publishEvent(event);
}
}
}
通過上面方法可以看出 Spring 的事件是通過 ApplicationEventMulticaster 廣播出去的,這個 ApplicationEventMulticaster 在 IoC 啟動流程 initApplicationEventMulticaster 方法中初始化。如果該容器還存在父容器,那也會以同樣的形式將事件發(fā)布給父容器的監(jiān)聽者。
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
// 解析事件類型
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
// 根據(jù)事件與事件類型獲取所有監(jiān)聽者
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
// 獲取異步執(zhí)行器
Executor executor = getTaskExecutor();
if (executor != null) {
// 如果執(zhí)行器部位 null,則異步執(zhí)行將事件發(fā)布給每一個監(jiān)聽者
executor.execute(() -> invokeListener(listener, event));
}
else {
// 同步發(fā)布事件
invokeListener(listener, event);
}
}
}
發(fā)布事件前會先獲取所有已注冊的監(jiān)聽器,而監(jiān)聽器早已在 IoC 啟動流程的 registerListeners 方法中注冊。獲取到所有事件監(jiān)聽器之后,就可以進(jìn)行事件發(fā)布了。發(fā)布的時候分為異步執(zhí)行與順序執(zhí)行,默認(rèn)情況下 executor 是沒有初始化的,因此是順序執(zhí)行。
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
// 獲取錯誤處理機制
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)聽器的 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 的事件通知機制流程就結(jié)束了,總的來說還是比較好理解的。
二、事件通知 demo
了解了事件通知機制的基本原理后,下面我們來寫個 demo 體驗一下監(jiān)聽器是如何使用的。
// 定義一個 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;
}
}
// 定義一個監(jiān)聽器1
public class EventDemo1Listener implements ApplicationListener<EventDemo> {
public void onApplicationEvent(EventDemo event) {
System.out.println(this + " receiver " + event.getMessage());
}
}
// 定義一個監(jiān)聽器2
public class EventDemo2Listener implements ApplicationListener<EventDemo> {
public void onApplicationEvent(EventDemo event) {
System.out.println(this + " receiver " + event.getMessage());
}
}
// 定義一個事件發(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"/>
編寫測試類:
@Test
public void eventTest() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation);
applicationContext.getBean("eventDemoPublish", EventDemoPublish.class).publish(applicationContext, "hello world");
}
控制臺輸出:

不知道你有沒有注意到,在發(fā)布事件的時候我們傳的發(fā)布者是 applicationContext,applicationContext 本身繼承自 ApplicationEventPublisher 接口,因此它本身也是一個事件發(fā)布者。
到此這篇關(guān)于Spring中的事件發(fā)布機制原理解析的文章就介紹到這了,更多相關(guān)Spring事件發(fā)布機制原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解SpringBoot 快速整合MyBatis(去XML化)
本篇文章主要介紹了詳解SpringBoot 快速整合MyBatis(去XML化),非常具有實用價值,需要的朋友可以參考下2017-10-10
Spring?BeanDefinition父子關(guān)系示例解析
這篇文章主要為大家介紹了Spring?BeanDefinition父子關(guān)系示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
java調(diào)用微信現(xiàn)金紅包接口的心得與體會總結(jié)
這篇文章主要介紹了java調(diào)用微信現(xiàn)金紅包接口的心得與體會總結(jié),有需要的朋友可以了解一下。2016-11-11
java8 Stream list to Map key 重復(fù) value合并到Collectio的操作
這篇文章主要介紹了java8 Stream list to Map key 重復(fù) value合并到Collectio的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
Java concurrency集合之ConcurrentSkipListSet_動力節(jié)點Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Java concurrency集合之ConcurrentSkipListSet的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06

