Spring中ApplicationEvent事件機(jī)制源碼詳解
一、ApplicationEvent應(yīng)用
先定義一個(gè) Event : ScorpiosEvent ,繼承 ApplicationEvent
public class ScorpiosEvent extends ApplicationEvent {
private String msg;
public ScorpiosEvent(Object source,String message) {
super(source);
this.msg = message;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}定義兩個(gè) Listener : ScorpiosListener 、 TestListener 。要加上 @Componen t注解,讓 Spring 容器管理。
// ScorpiosListener
@Component
public class ScorpiosListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if(event instanceof ScorpiosEvent){
System.out.println("ScorpiosEvent ..." + ((ScorpiosEvent) event).getMsg());
}else{
System.out.println("ScorpiosEvent....dododododododododo");
}
}
}// TestListener
@Component
public class TestListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if(event instanceof ScorpiosEvent){
System.out.println("TestListener ..." + ((ScorpiosEvent) event).getMsg());
}else{
System.out.println("TestListener....tttttttttttttttttttttt");
}
}
}入口函數(shù)
public static void main( String[] args )
{
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
ScorpiosEvent scorpiosEvent = new ScorpiosEvent("scorpios","aaaaaaaaaa");
ac.publishEvent(scorpiosEvent);
}
上面就是 Event 的具體的一個(gè)應(yīng)用, Listener 要被 Spring 容器管理。
從上面的代碼調(diào)用和日志可以看到,只要調(diào)用一個(gè)發(fā)布事件 ac.publishEvent(scorpiosEvent) ,所有的 Listener 都會(huì)被調(diào)用。注意,是所有的Listener,后面源碼分析。
從上面的輸出日志中可以看出, ScorpiosListener 、 TestListener 監(jiān)聽器都被調(diào)用了兩次,可代碼里,明明就調(diào)用了一次啊,為什么 System.out.println("ScorpiosEvent....dododododododododo") 這行代碼也會(huì)被輸出呢?這是為什么呢?下面就來分析下 Spring 事件機(jī)制的源碼吧。
二、ApplicationEvent源碼分析
Spring 的事件機(jī)制采用的是觀察者設(shè)計(jì)模式
1. Listener監(jiān)聽器的注冊(cè)過程
下面首先來了解下 Listener 監(jiān)聽器是何時(shí)被添加到 Spring 容器中的, Listener 被掃描后具體是存放在哪里的?下面先看一下 Spring 中大名鼎鼎的 refresh() 方法,如果對(duì)這個(gè)方法不了解,可以看下我對(duì) Spring 源碼解析的其他文章。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 準(zhǔn)備工作,包括設(shè)置啟動(dòng)時(shí)間,是否激活標(biāo)識(shí)位,初始化屬性源(property source)配置
prepareRefresh();
// 返回一個(gè)factory 為什么需要返回一個(gè)工廠? 因?yàn)橐獙?duì)工廠進(jìn)行初始化
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 準(zhǔn)備工廠
prepareBeanFactory(beanFactory);
try {
// 這個(gè)方法在當(dāng)前版本的spring是沒用任何代碼的,可能spring期待在后面的版本中去擴(kuò)展吧
postProcessBeanFactory(beanFactory);
// 調(diào)用BeanFactoryPostProcessors的后置處理器
// 在Spring環(huán)境中去執(zhí)行已經(jīng)被注冊(cè)的FactoryProcessors
// 設(shè)置執(zhí)行自定義的ProcessorBeanFactory和Spring內(nèi)部自己定義的
invokeBeanFactoryPostProcessors(beanFactory);
//-------------------到此spring工廠完成創(chuàng)建工作--------------------------
// 注冊(cè)BeanPostProcessor后置處理器
registerBeanPostProcessors(beanFactory);
initMessageSource();
// 初始化應(yīng)用事件廣播器
initApplicationEventMulticaster();
onRefresh();
// 注冊(cè)監(jiān)聽器
registerListeners();
// 實(shí)例化單實(shí)例非懶加載的Bean
finishBeanFactoryInitialization(beanFactory);
// 此處會(huì)發(fā)布一個(gè)事件:ContextRefreshedEvent
finishRefresh();
}
}
}主要看下面這四個(gè)方法:
- initApplicationEventMulticaster()
- registerListeners()
- finishBeanFactoryInitialization(beanFactory)
- finishRefresh()
注意:在上面這四個(gè)方法執(zhí)行之前,Spring的組件掃描工作已經(jīng)結(jié)束了,但Bean實(shí)例化還沒有。
2. initApplicationEventMulticaster()
此方法的作用是初始化應(yīng)用事件廣播器,這個(gè)廣播器是干嘛的呢?說的直白點(diǎn),里面存放了所有的 Listener
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 工廠里是否包含 “applicationEventMulticaster” Bean
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
} else {
// 沒有的話,Spring自己創(chuàng)建一個(gè)SimpleApplicationEventMulticaster
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
// 將自己創(chuàng)建的SimpleApplicationEventMulticaster放到Spring容器中,
// name 為:applicationEventMulticaster
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
}
}
}此處是Spring自己創(chuàng)建了一個(gè) SimpleApplicationEventMulticaster ,放入到 Spring 容器中,看斷點(diǎn)圖。

3. registerListeners()
此方法里面是從 Spring 容器中,拿到實(shí)現(xiàn)了 ApplicationListener 接口的所有 BeanName ,然后把它們名字添加到廣播器中的 this.defaultRetriever.applicationListenerBeans 中
protected void registerListeners() {
// 注冊(cè)靜態(tài)指定的監(jiān)聽器,沒有
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
}
// 從Spring容器中拿到所有實(shí)現(xiàn)了ApplicationListener接口的類,此處能拿到我們添加的兩個(gè)Listener
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
// 獲取上面創(chuàng)建的ApplicationEventMulticaster廣播器,把listenerBeanName放到廣播器中的
// this.defaultRetriever.applicationListenerBeans這個(gè)集合中,注意此處是放的是listenerBeanName
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (earlyEventsToProcess != null) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
4. finishBeanFactoryInitialization()
此方法就是實(shí)例化單例非懶加載的 Bean 。這個(gè)方法就不具體介紹了, Spring 源碼分析的其他文章中有詳細(xì)介紹。這地方給個(gè)斷點(diǎn)圖吧,看看執(zhí)行到此方法時(shí), Spring 容器中有哪些 Bean 。

從上面的斷點(diǎn)圖中可以看出, ScorpiosListener 、 TestListener 兩個(gè) Listener 已經(jīng)被掃描到,但還沒有被實(shí)例化,所以下面會(huì)進(jìn)行它們的實(shí)例化操作。那么這兩個(gè)Listener是怎么被保存到廣播器 ApplicationEventMulticaster 中的呢?答案是通過 ApplicationListenerDetector 這個(gè) BeanPostProcessor 后置處理器。
// ApplicationListenerDetector類
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 判斷當(dāng)前的Bean是不是實(shí)現(xiàn)了ApplicationListener接口
// 這兩個(gè)ScorpiosListener、TestListener肯定是的啊
// 此處的Bean已經(jīng)實(shí)例化好了
if (bean instanceof ApplicationListener) {
Boolean flag = this.singletonNames.get(beanName);
if (Boolean.TRUE.equals(flag)) {
// 將這個(gè)實(shí)現(xiàn)了ApplicationListener接口的Bean放到廣播器的
// this.defaultRetriever.applicationListeners屬性中
this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
} else if (Boolean.FALSE.equals(flag)) {
this.singletonNames.remove(beanName);
}
}
return bean;
}// AbstractApplicationContext類中方法
public void addApplicationListener(ApplicationListener<?> listener) {
if (this.applicationEventMulticaster != null) {
this.applicationEventMulticaster.addApplicationListener(listener);
}
this.applicationListeners.add(listener);
}
// AbstractApplicationEventMulticaster類中方法
public void addApplicationListener(ApplicationListener<?> listener) {
synchronized (this.retrievalMutex) {
Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
if (singletonTarget instanceof ApplicationListener) {
this.defaultRetriever.applicationListeners.remove(singletonTarget);
}
// 將listener放到廣播器的屬性中了?。。。?!
this.defaultRetriever.applicationListeners.add(listener);
this.retrieverCache.clear();
}
}
上面是 Listener 實(shí)例化和最終保存在哪里的源碼分析,下面要看一下,發(fā)布事件是怎么觸發(fā)監(jiān)聽器的調(diào)用的呢?
5. finishRefresh()
在此方法中, Spring 發(fā)布了一個(gè)事件: ContextRefreshedEvent 。
protected void finishRefresh() {
clearResourceCaches();
initLifecycleProcessor();
getLifecycleProcessor().onRefresh();
// 發(fā)布事件,就關(guān)注這一個(gè)方法!?。?!
publishEvent(new ContextRefreshedEvent(this));
LiveBeansView.registerApplicationContext(this);
}下面來看一下這個(gè) publishEvent() 方法
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
ApplicationEvent applicationEvent;
// 判斷這個(gè)event是不是ApplicationEvent實(shí)例
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
} else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
}
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
} else {
// 拿到廣播器,然后調(diào)用廣播器的multicastEvent()方法
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
} else {
this.parent.publishEvent(event);
}
}
}AbstractApplicationContext 類中的 getApplicationEventMulticaster() 方法
// 此方法拿到之前創(chuàng)建的廣播器applicationEventMulticaster
// 還記得是什么類型的嘛?對(duì),是它:SimpleApplicationEventMulticaster
ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
if (this.applicationEventMulticaster == null) {
// 拋異常代碼略
}
return this.applicationEventMulticaster;
}SimpleApplicationEventMulticaster 廣播器中的 multicastEvent() 方法
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
// 此getApplicationListeners(event, type)方法就是拿到廣播器里面的所有Listener
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
} else {
// 調(diào)用監(jiān)聽器的方法?。。?!
invokeListener(listener, event);
}
}
}
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
doInvokeListener(listener, event);
} else {
// 參數(shù)傳入的是listener,終于開始調(diào)用了?。。?!
doInvokeListener(listener, event);
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
// 調(diào)用Listener中僅有的onApplicationEvent()方法?。?!
listener.onApplicationEvent(event);
} catch (ClassCastException ex) {
// 拋異常代碼略
}
}終于分析完了。這里是 Spring 自己發(fā)布的一個(gè) Event ,所以會(huì)走到 ScorpiosListener 、 TestListener 兩個(gè)監(jiān)聽器里面的else代碼,打印 System.out.println("ScorpiosEvent....dododododododododo") , System.out.println("TestListener....tttttttttttttttttttttt") 這兩行代碼。

當(dāng)執(zhí)行到 ac.publishEvent(scorpiosEvent) 這行代碼發(fā)布 Event 事件時(shí),廣播器又會(huì)去調(diào)用所有的 Listener ,所以會(huì)有這兩行代 System.out.println("ScorpiosEvent ..." + ((ScorpiosEvent) event).getMsg()) 碼的輸出?。?!

三、 小結(jié)
Spring 中事件機(jī)制使用的是觀察者設(shè)計(jì)模式,其中對(duì)應(yīng)觀察者的四個(gè)角色分別為:
- 事件Event: ApplicationEvent 是所有事件對(duì)象的父類。 ApplicationEvent 繼承自 JDK 中的 EventObject ,所有的事件都需要繼承 ApplicationEvent ,并且通過 source 得到事件源
Spring 也為我們提供了很多內(nèi)置事件, ContextRefreshedEvent 、 ContextStartedEvent 、 ContextStoppedEvent 、 ContextClosedEvent 、 RequestHandledEvent 。
- 事件監(jiān)聽器: ApplicationListener ,也就是觀察者,繼承自 JDK 的 EventListener ,該類中只有一個(gè)方法 onApplicationEvent() ,當(dāng)監(jiān)聽的事件發(fā)生后該方法會(huì)被執(zhí)行
- 事件源: ApplicationContext , ApplicationContext 是 Spring 中的核心容器,在事件監(jiān)聽中 ApplicationContext 可以作為事件的發(fā)布者,也就是事件源。因?yàn)?ApplicationContext繼承自 ApplicationEventPublisher。在 ApplicationEventPublisher中定義了事件發(fā)布的方法:publishEvent(Object event)
- 事件管理:ApplicationEventMulticaster,用于事件監(jiān)聽器的注冊(cè)和事件的廣播。監(jiān)聽器的注冊(cè)就是通過它來實(shí)現(xiàn)的,它的作用是把 Applicationcontext發(fā)布的 Event廣播給它的監(jiān)聽器列表。 因?yàn)樗锩媸治账斜O(jiān)聽器。
Spring中的事件模型是一種簡單的、粗粒度的監(jiān)聽模型,當(dāng)有一個(gè)事件到達(dá)時(shí),所有的監(jiān)聽器都會(huì)接收到,并且作出響應(yīng),如果希望只針對(duì)某些類型進(jìn)行監(jiān)聽,需要在代碼中進(jìn)行控制。
當(dāng)ApplicationContext接收到事件后,事件的廣播是Spring內(nèi)部給我們做的,其實(shí)在Spring讀包掃描之后,將所有實(shí)現(xiàn)ApplicationListener的Bean找出來,注冊(cè)為容器的事件監(jiān)聽器。當(dāng)接收到事件的 時(shí)候,Spring會(huì)逐個(gè)調(diào)用事件監(jiān)聽器。
到此這篇關(guān)于Spring中ApplicationEvent事件機(jī)制源碼詳解的文章就介紹到這了,更多相關(guān)ApplicationEvent事件機(jī)制源碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java文件操作代碼片斷實(shí)例實(shí)現(xiàn)統(tǒng)計(jì)文件中字母出現(xiàn)的個(gè)數(shù)功能
本文介紹java讀文件實(shí)例,實(shí)現(xiàn)統(tǒng)計(jì)某一目錄下每個(gè)文件中出現(xiàn)的字母個(gè)數(shù)、數(shù)字個(gè)數(shù)、空格個(gè)數(shù)及行數(shù),除此之外沒有其他字符,大家參考使用吧2014-01-01
SpringBoot超詳細(xì)講解@Enable*注解和@Import
這篇文章主要介紹了SpringBoot?@Enable*注解和@Import,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
SpringMVC自定義消息轉(zhuǎn)換器的使用其實(shí)很簡單
這篇文章主要介紹了SpringMVC自定義消息轉(zhuǎn)換器的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
java如何從地址串中解析提取省市區(qū)(完美匹配中國所有地址)
這篇文章主要給大家介紹了關(guān)于java如何從地址串中解析提取省市區(qū)的相關(guān)資料,通過這個(gè)方法可以完美匹配中國所有地址,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
Java按照List內(nèi)存儲(chǔ)的對(duì)象的某個(gè)字段進(jìn)行排序的實(shí)例
下面小編就為大家?guī)硪黄狫ava按照List內(nèi)存儲(chǔ)的對(duì)象的某個(gè)字段進(jìn)行排序的實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12
Java開發(fā)學(xué)習(xí) Java數(shù)組操作工具
這篇文章主要為大家詳細(xì)介紹了自己編寫的Java數(shù)組操作工具,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
Java Web 實(shí)現(xiàn)QQ登錄功能一個(gè)帳號(hào)同一時(shí)間只能一個(gè)人登錄
對(duì)于一個(gè)帳號(hào)在同一時(shí)間只能一個(gè)人登錄,下文給大家介紹的非常詳細(xì),對(duì)java web qq 登錄功能感興趣的朋友一起看看吧2016-11-11

