Spring上下文啟動的時候將實現ApplicationListener介面的Bean新增到事件監聽者列表中,每次使用ApplicationEventPublisher釋出ApplicationEvent時,都會通知對該事件感興趣(監聽該事件)的Bean。
ApplicationContext繼承了ApplicationEventPublisher介面,從而擁有事件釋出的能力。但是實際ApplicationContext事件釋出委託給ApplicationEventMulticaster執行。
protected void publishEvent(Object event, @Nullable ResolvableType eventType) { // ...省略 if (this.earlyApplicationEvents != null) { this.earlyApplicationEvents.add(applicationEvent); } else { getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); } // ...省略}
相關關鍵類或註解
ApplicationEventPublisher:釋出事件ApplicationListener:事件監聽者ApplicationEvent:事件EventListener:事件和事件監聽繫結ApplicationEventMulticaster:釋出事件Spring容器在啟動的過程中也會發布各種事件,相應的元件監聽到之後完成各自的初始化工作,下面是Spring內建的事件。
ContextStartedEvent:Spring上下文啟動事件ContextRefreshedEvent:Spring上下文初始化或重新整理事件ContextStoppedEvent:Spring上下文停止事件ContextClosedEvent:Spring上下文關閉事件一、基於ApplicationListener實現事件監聽首先定一個事件,事件一定要繼承ApplicationEventpublic class DemoEvent extends ApplicationEvent { public DemoEvent(Object source) { super(source); }}
然後定義一個類實現ApplicationListener介面
@Componentpublic class DemoListener implements ApplicationListener<DemoEvent> { @Override public void onApplicationEvent(DemoEvent event) { System.out.println("收到訊息: " + event); }}
釋出事件applicationContext.publishEvent(new DemoEvent(new Object()));// 收到訊息: com.example.demo.applicationevent.DemoEvent[source=java.lang.Object@3cb173db]
雖然完成了事件監聽,但是這種實現方案有點不太好,監聽類必須實現特定的ApplicationListener介面,事件也必須繼承ApplicationEvent類且一個類只能處理一個事件。接下來介紹第二種方式可以完全避免這種問題。
二、基於@EventListener實現事件監聽public class DemoEvent2 {}@Componentpublic class DemoListener2 { @EventListener(DemoEvent2.class) public void onDemoEvent(DemoEvent2 demoEvent2) { System.out.println("@EventListener: " + demoEvent2); }}// 輸出日誌2020-04-28 23:52:59.187 INFO 4425 --- [ main] com.example.demo.DemoApplication : 釋出DemoEvent2事件2020-04-28 23:52:59.187 INFO 4425 --- [ main] c.e.demo.eventlistener.DemoListener2 : @EventListener: com.example.demo.eventlistener.DemoEvent2@75d7297d
基於@EventListener的監聽類不需要實現ApplicationListener,事件也不需要繼承ApplicationEvent類,且可以在類裡面宣告多個方法處理不同的事件。實際開發中更傾向於這種實現方案。
三、非同步監聽-@Async從上面的輸出日誌中可以看出,事件釋出和監聽是處在同一個執行緒中,有時候我們可能需要實現非同步監聽,可以藉助@Async和自定義ApplicationEventMulticaster兩種方式實現訊息的非同步監聽。
@Async修改程式碼如下
@Slf4j@Component@EnableAsyncpublic class DemoListener2 { @Async @EventListener(DemoEvent2.class) public void onDemoEvent(DemoEvent2 demoEvent2) { log.info("@EventListener: " + demoEvent2); }}// 輸出日誌2020-04-28 23:56:30.252 INFO 4508 --- [ main] com.example.demo.DemoApplication : 釋出DemoEvent2事件2020-04-28 23:56:30.310 INFO 4508 --- [ task-1] c.e.demo.eventlistener.DemoListener2 : @EventListener: com.example.demo.eventlistener.DemoEvent2@184bc84
添加註解啟用非同步,並在指定方法上面新增@Async註解。從日誌中可以看出事件釋出和監聽已經處在兩個不同的執行緒中。
為什麼宣告一個這樣的Bean就可以完成非同步呢?
在開始的時候提到過ApplicationContext將事件釋出委託給ApplicationEventMulticaster執行,接下來透過原始碼看ApplicationContext如何委託事件給ApplicationEventMulticaster。
AbstractApplicationContext獲取ApplicationEventMulticaster物件
ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException { if (this.applicationEventMulticaster == null) { throw new IllegalStateException("ApplicationEventMulticaster not initialized - " + "call 'refresh' before multicasting events via the context: " + this); } return this.applicationEventMulticaster;}
接下來看AbstractApplicationContext如何初始化applicationEventMulticaster物件
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { try { // 省略無關程式碼 // Initialize event multicaster for this context. initApplicationEventMulticaster(); } catch (BeansException ex) { } }}
可以看出來在呼叫refresh的時候會初始化applicationEventMulticaster
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";protected void initApplicationEventMulticaster() { ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class); if (logger.isTraceEnabled()) { logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]"); } } else { this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory); beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster); if (logger.isTraceEnabled()) { logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " + "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]"); } }}
初始化的是首先會嘗試從Spring獲取指定name的Bean,如果沒有獲取到,則新建一個例項,並註冊到IoC容器中,到這裡就明白為什麼宣告一個Bean之後就完成了非同步的操作。因為我們提前聲明瞭一個applicationEventMulticaster Bean物件,所以Spring會把這個物件當成預設的事件釋出工具。自定義物件指定了執行緒池,所以事件釋出和監聽會處在不同的執行緒池中。
這種做法會導致由該物件釋出的所有事件都是非同步處理,實際開發過程中推薦使用@Async註解實現非同步監聽邏輯,這樣可以針對性對指定事件監聽非同步處理。