本文僅供學(xué)習(xí)交流使用哄辣,侵權(quán)必刪。
不作商業(yè)用途远豺,轉(zhuǎn)載請(qǐng)注明出處
1. 概述
在Spring Framework中肩祥,ApplicationContext是通過org.springframework.context.ApplicationEvent
和org.springframework.context.ApplicationListener
管理事件飞几。一個(gè)Bean如果實(shí)現(xiàn)了ApplicationListener接口砚哆,這個(gè)Bean就會(huì)監(jiān)聽指定類型的事件,只要這個(gè)指定事件在ApplicationContext中被發(fā)布屑墨,這個(gè)監(jiān)聽器就會(huì)被通知躁锁。這是一種典型的觀察者模式實(shí)現(xiàn)
當(dāng)前使用版本是Spring Framework 5.2.2.RELEASE
1. 1Spring的內(nèi)建事件
事件類型 | 說明 |
---|---|
ContextRefreshedEvent | 當(dāng)ApplicationContext初始化完成的時(shí)候會(huì)發(fā)布該事件 |
ContextStartedEvent | 當(dāng)ApplicationContext通過org.springframework.context.ConfigurableApplicationContext#start 方法啟動(dòng)會(huì)發(fā)布該事件 |
ContextStoppedEvent | 當(dāng)ApplicationContext通過org.springframework.context.ConfigurableApplicationContext#stop 方法停止容器會(huì)發(fā)布該事件 |
ContextClosedEvent | 當(dāng)ApplicationContext通過org.springframework.context.ConfigurableApplicationContext#close 方法或者通過JVM的shutdown hook關(guān)閉容器的會(huì)發(fā)布該事件 |
RequestHandledEvent | web環(huán)境事件,當(dāng)一個(gè)http請(qǐng)求完成后發(fā)布該事件卵史。注意這個(gè)事件只有使用了Spring的org.springframework.web.servlet.DispatcherServlet 的web程序下才能發(fā)布 |
ServletRequestHandledEvent | RequestHandledEvent的子類战转,擴(kuò)展了一下關(guān)于Servlet上下文信息 |
除此之外,還能能夠通過繼承org.springframework.context.ApplicationEvent
實(shí)現(xiàn)自定義事件以躯。下面將展示如何在Spring環(huán)境中實(shí)現(xiàn)事件的監(jiān)聽以及發(fā)布自定義事件槐秧。
2. 事件監(jiān)聽以及發(fā)布的代碼實(shí)現(xiàn)
org.springframework.context.ApplicationListener
監(jiān)聽器注冊(cè)到應(yīng)用上下文的方式有多種:
- 可以通過常規(guī)Spring Bean的方式注冊(cè)。
- 可以構(gòu)建一個(gè)外部對(duì)象通過
ConfigurableApplicationContext#addApplicationListener
注冊(cè)到應(yīng)用上下文中忧设。 - 另外刁标,從Spring4.2后開始支持以注解的方式注冊(cè)。
并且監(jiān)聽器的回調(diào)方法執(zhí)行有同步和異步兩種方式址晕。下面將展示具體的代碼實(shí)現(xiàn)膀懈。
2.1 通過注冊(cè)Spring Bean的方式注冊(cè)監(jiān)聽器
- 首先定義一個(gè)監(jiān)聽器實(shí)現(xiàn)類MyApplicationListener,并監(jiān)聽
org.springframework.context.event.ContextRefreshedEvent
事件谨垃。后面的代碼實(shí)現(xiàn)基本都是繼續(xù)沿用這個(gè)實(shí)現(xiàn)類
public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println ("MyApplicationListener#onApplicationEvent event:" + event);
}
}
-
編寫Main方法啟動(dòng)Spring容器并注冊(cè)監(jiān)聽器
public class SpringEventDemo { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (); //注冊(cè)監(jiān)聽器 applicationContext.register (MyApplicationListener.class); //啟動(dòng)Spring應(yīng)用上下文 applicationContext.refresh (); //關(guān)閉Spring應(yīng)用上下文 applicationContext.close(); } }
這里Main方法執(zhí)行后這個(gè)console會(huì)打印
MyApplicationListener#onApplicationEvent event:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@49c2faae, started on Mon Apr 26 14:17:44 CST 2021]
2.2 通過ConfigurableApplicationContext#addApplicationListener注冊(cè)監(jiān)聽器
- 編寫Main方法啟動(dòng)Spring容器并注冊(cè)監(jiān)聽器
public class SpringEventDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext ();
//實(shí)例化監(jiān)聽器對(duì)象
MyApplicationListener myApplicationListener = new MyApplicationListener ();
//將監(jiān)聽器對(duì)象添加到Spring應(yīng)用上下文
applicationContext.addApplicationListener (myApplicationListener);
//啟動(dòng)Spring應(yīng)用上下文
applicationContext.refresh ();
//關(guān)閉Spring應(yīng)用上下文
applicationContext.close();
}
}
- 這里Main方法執(zhí)行后這個(gè)console會(huì)打印
MyApplicationListener#onApplicationEvent event:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@49c2faae, started on Mon Apr 26 14:17:44 CST 2021]
2.3通過注解方式注冊(cè)&設(shè)置監(jiān)聽器異步回調(diào)
- 通過@EventListener標(biāo)記在方法上設(shè)置監(jiān)聽器回調(diào)方法
- 通過@Async和@EnableAsync開啟異步執(zhí)行
- 開啟異步回調(diào)監(jiān)聽器除了通過注解的方式启搂,還能獲取ApplicationContext的SimpleApplicationEventMulticaster,調(diào)用
SimpleApplicationEventMulticaster#setTaskExecutor
設(shè)置線程池開啟異步執(zhí)行硼控。但這種方式要注意我們需要自己通過代碼進(jìn)行線程池關(guān)閉,Spring應(yīng)用上下文不會(huì)關(guān)閉我們?cè)O(shè)置進(jìn)去的線程池胳赌。
- 開啟異步回調(diào)監(jiān)聽器除了通過注解的方式启搂,還能獲取ApplicationContext的SimpleApplicationEventMulticaster,調(diào)用
@EnableAsync
public class AnnotatedAsyncEventListenerDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext ();
applicationContext.register (AnnotatedAsyncEventListenerDemo.class);
applicationContext.refresh ();
applicationContext.publishEvent (new MyApplicationEvent ("AnnotatedAsyncEventListenerDemo#MyApplicationEvent"));
applicationContext.close ();
}
@Async
@EventListener
public void onListenerEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.out.printf ("事件執(zhí)行當(dāng)前線程:%s,事件為:%s\n",Thread.currentThread ().getName (),contextRefreshedEvent);
}
}
- Main方法執(zhí)行后這個(gè)console會(huì)打印,這里可以看到處理事件的線程并不是main線程
事件執(zhí)行當(dāng)前線程:SimpleAsyncTaskExecutor-1,事件為:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@5d099f62, started on Mon Apr 26 17:04:34 CST 2021]
2.4 自定義事件
嘗試自定義一個(gè)事件并進(jìn)行發(fā)布牢撼。
- 定義自定義事件MyApplicationEvent
public class MyApplicationEvent extends ApplicationEvent {
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public MyApplicationEvent(String source) {
super (source);
}
@Override
public String getSource() {
return (String) super.getSource ();
}
}
- 定義監(jiān)聽自定義事件的監(jiān)聽器MyEventListener
public class MyEventListener implements ApplicationListener<MyApplicationEvent> { @Override public void onApplicationEvent(MyApplicationEvent event) { System.out.printf ("執(zhí)行事件:[%s]\n",event); } }
- Main方法編寫測(cè)試,這里通過
org.springframework.context.ApplicationEventPublisherAware
回調(diào)接口發(fā)布事件
public class CustomizedEventDemo implements ApplicationEventPublisherAware {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext ();
applicationContext.register (CustomizedEventDemo.class);
applicationContext.addApplicationListener (new MyEventListener ());
applicationContext.refresh ();
applicationContext.close ();
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
applicationEventPublisher.publishEvent (new MyApplicationEvent ("hello,setApplicationEventPublisher"));
}
}
2.5 事件異常處理器
在Spring 3.0之后提供了一個(gè)org.springframework.util.ErrorHandler
能夠處理事件異常的情況,如圖2-5-1匈织。下面將展示如何向Spring應(yīng)用上下文中注冊(cè)事件異常處理器
public class ErrorHandlerDemo {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext ();
applicationContext.addApplicationListener (new MyEventListener ());
applicationContext.refresh ();
ApplicationEventMulticaster applicationEventMulticaster = applicationContext.getBean (AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (applicationEventMulticaster instanceof SimpleApplicationEventMulticaster) {
SimpleApplicationEventMulticaster simpleApplicationEventMulticaster =
(SimpleApplicationEventMulticaster) applicationEventMulticaster;
/**
* Spring事件錯(cuò)誤處理代碼
* SimpleApplicationEventMulticaster中設(shè)置ErrorHandler
* {@link org.springframework.util.ErrorHandler}
*/
simpleApplicationEventMulticaster.setErrorHandler (new ErrorHandler () {
@Override
public void handleError(Throwable t) {
System.err.println ("ErrorHandler處理事件錯(cuò)誤浪默," + t);
}
});
/**
設(shè)置一個(gè)主動(dòng)拋出異常的監(jiān)聽器
*/
simpleApplicationEventMulticaster.addApplicationListener (new ApplicationListener<MyApplicationEvent> () {
@Override
public void onApplicationEvent(MyApplicationEvent event) {
throw new RuntimeException ("主動(dòng)拋出異常,測(cè)試事件錯(cuò)誤處理");
}
});
}
// 發(fā)布MyApplicationEvent事件觸發(fā)異步事件
applicationEventMulticaster.multicastEvent (new MyApplicationEvent ("AsyncEventListenerDemo"));
System.out.println ("關(guān)閉Spring應(yīng)用上下文");
applicationContext.close ();
}
}
- 這里Main方法會(huì)輸出
ErrorHandler處理事件錯(cuò)誤缀匕,java.lang.RuntimeException: 主動(dòng)拋出異常纳决,測(cè)試事件錯(cuò)誤處理
以上就是Spring事件相關(guān)的操作,此外如果我們需要控制監(jiān)聽器的調(diào)用順序乡小,我們可以通過@Order控制具體的執(zhí)行順序阔加。
3.Spring Event源碼分析
3.1 容器啟動(dòng)時(shí),事件的相關(guān)源碼
這里從org.springframework.context.support.AbstractApplicationContext#refresh
方法開始入手满钟。首先refresh方法調(diào)用的prepareRefresh
方法是容器啟動(dòng)前的準(zhǔn)備胜榔,這里的相關(guān)操作是初始化一個(gè)名為earlyApplicationEvents
的Set<ApplicationEvent>
,這是用于存放早期事件的一個(gè)容器湃番,這個(gè)容器的出現(xiàn)了是為了修復(fù)Spring之前的一個(gè)缺陷夭织,這里切換到Spring Framework 3.0的源碼并嘗試分析這個(gè)缺陷是什么以及到底如何出現(xiàn)的。
這里首先提前說一下吠撮,Spring應(yīng)用上下文擁有事件發(fā)布的能力尊惰,該能力基于其成員屬性org.springframework.context.event.ApplicationEventMulticaster
這個(gè)接口的實(shí)現(xiàn)類實(shí)現(xiàn)的
- 首先先看一下refresh方法的整體流程,如圖3-1-1所示
- 首先會(huì)做啟動(dòng)前準(zhǔn)備prepareBeanFactory
- 然后調(diào)用invokeBeanFactoryPostProcessor回調(diào)
org.springframework.beans.factory.config.BeanFactoryPostProcessor
的方法 - 再望后執(zhí)行initApplicationEventMulticaster泥兰,這個(gè)方法是實(shí)例化
org.springframework.context.event.ApplicationEventMulticaster
- 最后調(diào)用registerListeners,將用戶的監(jiān)聽器注冊(cè)到容器中
-
進(jìn)入
AbstractApplicationContext#prepareRefresh
弄屡,當(dāng)時(shí)并沒有初始化早期事件的容器,如圖3-1-2
導(dǎo)致缺陷發(fā)生的場(chǎng)景如下鞋诗,假設(shè)創(chuàng)建一個(gè)類是實(shí)現(xiàn)了 BeanFactoryPostProcessor以及ApplicationContextAware接口的膀捷。ApplicationContext是有發(fā)布事件的能力,其能力依賴于ApplicationEventMulticaster削彬。當(dāng)我們?cè)谕ㄟ^ApplicationContextAware回調(diào)獲取ApplicaitonContext實(shí)例后全庸,然后在BeanFactoryPostProcessor的回調(diào)接口中利用返回的ApplicationContext實(shí)例發(fā)布實(shí)現(xiàn),Spring應(yīng)用上下文就會(huì)拋出異常融痛,因?yàn)閺纳厦娴恼{(diào)用順序來看糕篇,BeanFactoryPostProcessor的回調(diào)時(shí)間早于ApplicationEventMulticaster的實(shí)例化,這里的ApplicationEventMulticaster還是null的所以會(huì)拋出NPE酌心。下面展示一下異常的示例
定義一個(gè)調(diào)用類BugInvoker
public class BugInvoker implements BeanFactoryPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
applicationContext.publishEvent (new MyApplicationEvent ("hello world"));
}
}
- 編寫Main方法測(cè)試
public class Spring3BugDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext ();
//注冊(cè)到容器中
applicationContext.register (BugInvoker.class);
applicationContext.addApplicationListener (new ApplicationListener () {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println (event);
}
});
applicationContext.refresh ();
applicationContext.close ();
}
}
- 這里是啟動(dòng)后拋出的具體異常信息,異常信息告訴用戶ApplicationEventMulticaster還沒有實(shí)例化完成
Exception in thread "main" java.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.context.annotation.AnnotationConfigApplicationContext@378fd1ac: startup date [Tue Apr 27 14:46:22 CST 2021]; root of context hierarchy
at org.springframework.context.support.AbstractApplicationContext.getApplicationEventMulticaster(AbstractApplicationContext.java:307)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:294)
at com.kgyam.event.springEvent.Spring3BugDemo$BugInvoke.postProcessBeanFactory(Spring3BugDemo.java:48)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:624)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:614)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:398)
at com.kgyam.event.springEvent.Spring3BugDemo.main(Spring3BugDemo.java:31)
- 而3.0之后加入的早期事件容器就是為了修復(fù)這個(gè)缺陷拌消,對(duì)早于ApplicationEventMulticaster實(shí)例化前發(fā)布的事件先緩存在這個(gè)容器中,等待ApplicationEventMulticaster實(shí)例化完成后再對(duì)這個(gè)容器里的事件進(jìn)行發(fā)布。
3.2 監(jiān)聽器注冊(cè)
監(jiān)聽器的注冊(cè)比較簡(jiǎn)單,通過org.springframework.context.support.AbstractApplicationContext#addApplicationListener
作為入口
-
將監(jiān)聽器添加到ApplicationEventMulticaster中墩崩,如圖3-2-1
-
org.springframework.context.event.AbstractApplicationEventMulticaster#addApplicationListener
中的添加監(jiān)聽器邏輯是排除掉重復(fù)的監(jiān)聽器以免發(fā)生二次調(diào)用氓英,然后就會(huì)把這個(gè)監(jiān)聽器對(duì)象添加到監(jiān)聽器列表中并清理監(jiān)聽器緩存Map,如圖3-2-2
到這里就基本完成了添加監(jiān)聽器的過程了鹦筹,接下來看下事件發(fā)布相關(guān)源碼铝阐。
3.3事件發(fā)布
事件發(fā)布通過org.springframework.context.support.AbstractApplicationContext#publishEvent
作為入口
-
首先根據(jù)發(fā)布的事件對(duì)象,如果是非ApplicationEvent類型的就會(huì)將其轉(zhuǎn)換為PayloadApplicationEvent對(duì)象铐拐,如圖3-3-1
- 然后就是如果earlyApplicationEvents不為null就會(huì)將其存放到早期事件容器中徘键,這里是表示applicationEventMulticaster對(duì)象沒有初始化完成,將事件緩存到該早期事件容器中遍蟋。在applicationEventMulticaster初始化完成并發(fā)布所有早期事件容器里面的事件后吹害,會(huì)將該容器設(shè)置為null。
- 如果applicationEventMulticaster應(yīng)用事件廣播器已經(jīng)實(shí)例化虚青,那么就會(huì)調(diào)用
org.springframework.context.event.ApplicationEventMulticaster#multicastEvent
發(fā)布事件它呀,如圖3-3-2
- 往下進(jìn)入ApplicationEventMulticaster的實(shí)現(xiàn)類
org.springframework.context.event.SimpleApplicationEventMulticaster
看下具體的事件發(fā)布邏輯,如圖3-3-3
- 調(diào)用getApplicationListeners方法獲取對(duì)應(yīng)事件類型對(duì)應(yīng)的監(jiān)聽器列表棒厘,這其中的邏輯相對(duì)是比較多的纵穿。
- 首先會(huì)根據(jù)事件類型和事件源類型生成對(duì)應(yīng)的監(jiān)聽器緩存key對(duì)象
org.springframework.context.event.AbstractApplicationEventMulticaster.ListenerCacheKey
,根據(jù)這個(gè)key從retrieverCache(Map對(duì)象奢人,如圖3-3-4)獲取監(jiān)聽器列表對(duì)象org.springframework.context.event.AbstractApplicationEventMulticaster.ListenerRetriever
,這個(gè)ListenerRetriever是存放了事件類型相關(guān)的監(jiān)聽器列表谓媒, -
如果沒有從緩存中獲取到對(duì)應(yīng)的監(jiān)聽器列表對(duì)象,就從defaultRetriever(存放所有監(jiān)聽器的對(duì)象何乎,圖3-3-5)中找到匹配的監(jiān)聽器生成對(duì)應(yīng)的ListenerRetriever并重新緩存到retrieverCache中篙耗,最后返回監(jiān)聽器列表。
-
返回監(jiān)聽器列表后就通過invokeListener遍歷回調(diào)監(jiān)聽器宪赶,如圖3-3-6。這里try-catch將捕獲監(jiān)聽器拋出的異常并交給ErrorHandler處理脯燃,如圖3-3-7
-
到這里事件發(fā)布流程基本就結(jié)束了搂妻。但還有一個(gè)地方需要注意,我們回到
org.springframework.context.event.ApplicationEventMulticaster#multicastEvent
這里辕棚,如果應(yīng)用上下文存在父子關(guān)系的話欲主,該事件就會(huì)傳遞到父的應(yīng)用上下文中,這種情況下注意出現(xiàn)事件被多次處理的情況逝嚎,如圖3-3-8扁瓢。
3.4 @EventListener注冊(cè)過程
在org.springframework.context.event.EventListener
的注釋中有提及到該注解是通過一個(gè)內(nèi)建bean對(duì)象org.springframework.context.event.EventListenerMethodProcessor
進(jìn)行處理的,如圖3-4-1所示补君。
-
首先先看下EventListenerMethodProcessor類,如圖3-4-2。對(duì)于處理EventListener注解的核心方法是在afterSingletonsInstantiated方法(實(shí)例化完成后處理接口
org.springframework.beans.factory.SmartInitializingSingleton
的接口方法)荤西。將斷點(diǎn)打在這個(gè)方法上作為入口看下對(duì)應(yīng)的處理邏輯。
該方法首先會(huì)獲取容器中所有的beanName并遍歷敞掘,然后根據(jù)beanName在容器中獲取其對(duì)應(yīng)的Class類型。
-
獲取到Class類型后找到所有標(biāo)記了注解@EventListener的方法并放到一個(gè)名為annotatedMethods的Map中楣铁,如圖3-4-3玖雁。
-
如果annotatedMethods非空,就遍歷這個(gè)Map并通過
org.springframework.context.event.EventListenerFactory
的實(shí)現(xiàn)類對(duì)其進(jìn)行處理盖腕,如圖3-4-5赫冬。這里會(huì)針對(duì)每個(gè)標(biāo)記了注解的Method對(duì)象創(chuàng)建一個(gè)對(duì)應(yīng)的事件監(jiān)聽適配器并通過應(yīng)用上下文的addApplicationContext方法將其添加進(jìn)去。
-
EventListenerFactory在Spring的內(nèi)建實(shí)現(xiàn)只有一個(gè)
org.springframework.context.event.DefaultEventListenerFactory
溃列,如圖3-4-6劲厌。該接口主要有兩個(gè)方法,第一個(gè)是判斷是否支持該Method對(duì)象哭廉,DefaultEventListenerFactory默認(rèn)返回true脊僚。第二個(gè)方法是針對(duì)該方法創(chuàng)建一個(gè)事件監(jiān)聽器,DefaultEventListenerFactory會(huì)創(chuàng)建一個(gè)事件監(jiān)聽適配器org.springframework.context.event.ApplicationListenerMethodAdapter#ApplicationListenerMethodAdapter
遵绰。
@EventListener注解的處理基本到這里就結(jié)束了辽幌。
4.總結(jié)
Spring Event使用的是典型的觀察者模型,這種情況能夠讓后監(jiān)聽器更加符合單一職責(zé)原則椿访,并且能夠提高系統(tǒng)的擴(kuò)展性乌企。同時(shí)我們還知道了Spring應(yīng)用上下文ApplicationContext是擁有事件發(fā)布的能力的,其能力是依賴于org.springframework.context.event.ApplicationEventMulticaster
成玫。
最后還需要注意的是加酵,如果使用Spring 3.0這種早期版本需要注意事件發(fā)布必須晚于事件廣播器實(shí)例化。