首頁>技術>

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實現事件監聽首先定一個事件,事件一定要繼承ApplicationEvent
public 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註解實現非同步監聽邏輯,這樣可以針對性對指定事件監聽非同步處理。

18
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Python庫大全,建議收藏留用