Spring 之 事件機制詳解

概念

Spring事件機制分爲事件發佈器(EventPublisher)、事件監聽器(EventListener)和事件多播器(ApplicationEventMulticaster)。Spring事件機制對應常用設計模式之觀察者模式,主要就是用來解耦。
Spring 的 ApplicationContext 提供了支持事件和代碼中監聽器的功能。我們可以創建 bean 用來監聽在 ApplicationContext 中發佈的事件。ApplicationEvent 類和在ApplicationContext 接口中處理的事件,如果一個 bean 實現了 ApplicationListener 接口,當一個 ApplicationEvent 被髮布以後,bean 會自動被通知。

使用

事件發佈器

需要實現ApplicationEventPublisherAware這個Aware接口,廣播事件需要利用到applicationEventPublisher。用戶發佈的事件類型可以是:

  1. 用戶可以繼承ApplicationEvent從而自定義Event類型
  2. 也可以使用任意Object類型,但是如果event真實類型不是ApplicationEvent的話,那麼event會被封裝成PayloadApplicationEvent
@Component
public class StringEventPublish implements ApplicationEventPublisherAware {
   private ApplicationEventPublisher applicationEventPublisher;

   @Override
   public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
       this.applicationEventPublisher = applicationEventPublisher;
   }

   public void saySomething(String msg){
       System.out.println("發佈者線程名:" + Thread.currentThread().getName());
       applicationEventPublisher.publishEvent(msg);
   }
}

事件監聽器

事件監聽者需要實現ApplicationListener接口,由於監聽的是String類型的事件會被封裝成PayloadApplicationEvent,所以此處類型是PayloadApplicationEvent。

@Component
public class StringListener implements ApplicationListener<PayloadApplicationEvent<String>> {
   public void onApplicationEvent(PayloadApplicationEvent event) {
       Object msg = event.getPayload();
       System.out.println("監聽器線程名:" + Thread.currentThread().getName());
       System.out.println("ListenerA receive:" + msg);
   }
}

關於發佈出去的事件,哪些監聽者會監聽到?

  1. 發佈的事件類型是ApplicationEvent的實現類A,那麼所有監聽者的onApplicationEvent的參數類型是A或者A的子類都會收到事件。
  2. 發佈的事件類型是不是ApplicationEvent類型,類型是B。這種情況下,最終事件會被包裝成PayloadApplicationEvent<B>,那麼所有監聽者方法onApplicationEvent的參數是PayloadApplicationEvent<B>的監聽者會收到。假設有C是B的父類,且有一個監聽者X監聽PayloadApplicationEvent<C>,那X是收不到PayloadApplicationEvent類型的事件的。

事件多播器

Spring 在創建默認的事件多播器 SimpleApplicationEventMulticaster 時,taskExecutor 屬性默認是null,所以默認情況下所有的監聽器的 onApplicationEvent 是直接在當前線程(事件發佈者所在線程)中調用。如果 onApplicationEvent 有阻塞操作也會導致事件發佈者被阻塞,後續的其他監聽器也會被阻塞無法調用。
我們可以爲默認事件多播器設置 taskExecutor 屬性,從而達到異步監聽的目的。

XML 配置方式:

<bean id="executorService" class="java.util.concurrent.Executors" factory-method="newScheduledThreadPool">
</bean>

<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
       <property name="taskExecutor" ref="executorService">
       </property>
</bean>

使用註解的話,可以在Spring應用啓動完成後進行設置:

@Component
@AllArgsConstructor
public class EventMulticasterConfig implements ApplicationRunner {
   private final SimpleApplicationEventMulticaster simpleApplicationEventMulticaster;

   @Override
   public void run(ApplicationArguments args){
       ExecutorService executorService = Executors.newScheduledThreadPool(10);
       simpleApplicationEventMulticaster.setTaskExecutor(executorService);
   }
}

原理

可以看到事件多播器的類圖,其默認實現只有一個 SimpleApplicationEventMulticaster。
在這裏插入圖片描述
所以我們先來看下多播器是在哪裏進行初始化的,在 AbstractApplicationContext.refresh() 方法中進行刷新時有調用 initApplicationEventMulticaster 方法進行初始化容器事件廣播器,並放入至上下文的applicationEventMulticaster 屬性中。所以我們來看下其初始化代碼:

protected void initApplicationEventMulticaster() {
	//獲取beanFactory
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	//從beanFactory中獲取事件廣播器
	if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
		//如果有,則賦值給applicationEventMulticaster
		this.applicationEventMulticaster =
				beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
		if (logger.isTraceEnabled()) {
			logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
		}
	}
	else {
		//如果沒有,則創建一個SimpleApplicationEventMulticaster
		this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
		//講創建的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() + "]");
		}
	}
}

在 IOC 容器初始化完成後,事件廣播器也初始化完成。我們就可以直接調用 publishEvent 進行發佈事件,就根據這個方法看看是如何實現的。跳轉到 AbstractApplicationContext.publishEvent() 方法,源碼如下:

public void publishEvent(Object event) {
	publishEvent(event, null);
}

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
	Assert.notNull(event, "Event must not be null");

	// 判斷事件類型是否爲ApplicationEvent,如果不是則封裝成PayloadApplicationEvent
	ApplicationEvent applicationEvent;
	if (event instanceof ApplicationEvent) {
		applicationEvent = (ApplicationEvent) event;
	}
	else {
		applicationEvent = new PayloadApplicationEvent<>(this, event);
		if (eventType == null) {
			eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
		}
	}

	// 在 IOC 初始化過程中的早期事件需要立即進行發佈
	if (this.earlyApplicationEvents != null) {
		this.earlyApplicationEvents.add(applicationEvent);
	}
	else {
		//使用默認多播器進行事件發佈
		getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
	}
	
	//調用父類上下文進行發佈事件
	if (this.parent != null) {
		if (this.parent instanceof AbstractApplicationContext) {
			((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
		}
		else {
			this.parent.publishEvent(event);
		}
	}
}

跳轉到 SimpleApplicationEventMulticaster.multicastEvent() ,將給定的應用程序事件多播到適當的偵聽器上。我們也可以上看上面所說的關於線程池的判斷,所以需要設置 taskExecutor 進行異步化監聽處理。

public void multicastEvent(ApplicationEvent event) {
	//將給定的應用程序事件多播到適當的偵聽器
	multicastEvent(event, resolveDefaultEventType(event));
}

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	// 獲取到事件對應的分解類型
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	// 獲取到多播器的當前線程池
	Executor executor = getTaskExecutor();
	// 獲取當前應用中與給定事件類型匹配的ApplicationListeners的監聽器集合,不符合的監聽器會被排除在外。再循環執行
	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		// 如果線程池不爲空,則通過線程池異步執行
		if (executor != null) {
			executor.execute(() -> invokeListener(listener, event));
		}
		else {
			// 否則由當前線程執行
			invokeListener(listener, event);
		}
	}
}

invokeListener 方法比較簡單,就是調用監聽器的監聽方法 onApplicationEvent 進行處理事件。

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
	ErrorHandler errorHandler = getErrorHandler();
	if (errorHandler != null) {
		try {
			doInvokeListener(listener, event);
		}
		catch (Throwable err) {
			errorHandler.handleError(err);
		}
	}
	else {
		doInvokeListener(listener, event);
	}
}

@SuppressWarnings({"rawtypes", "unchecked"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
	try {
		// 實際監聽器接受該事件並處理
		listener.onApplicationEvent(event);
	}
	catch (ClassCastException ex) {
		String msg = ex.getMessage();
		if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
			Log logger = LogFactory.getLog(getClass());
			if (logger.isTraceEnabled()) {
				logger.trace("Non-matching event type for listener: " + listener, ex);
			}
		}
		else {
			throw ex;
		}
	}
}

還有一個重要的方法是 getApplicationListeners ,如何返回與給定事件類型匹配的ApplicationListeners的集合,並將不匹配的監聽器會盡早被排除在外。

protected Collection<ApplicationListener<?>> getApplicationListeners(
		ApplicationEvent event, ResolvableType eventType) {
	// 最初發送事件的對象
	Object source = event.getSource();
	Class<?> sourceType = (source != null ? source.getClass() : null);
	// 將給定事件類型與源類型進行封裝
	ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

	// Quick check for existing entry on ConcurrentHashMap...
	ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
	if (retriever != null) {
		return retriever.getApplicationListeners();
	}

	if (this.beanClassLoader == null ||
			(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
					(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
		// 完全同步構建和緩存ListenerRetriever
		synchronized (this.retrievalMutex) {
			retriever = this.retrieverCache.get(cacheKey);
			if (retriever != null) {
				return retriever.getApplicationListeners();
			}
			retriever = new ListenerRetriever(true);
			//實際檢索給定事件和源類型的應用程序監聽器,將過濾後的監聽器集合進行返回
			Collection<ApplicationListener<?>> listeners =
					retrieveApplicationListeners(eventType, sourceType, retriever);
			this.retrieverCache.put(cacheKey, retriever);
			return listeners;
		}
	}
	else {
		// No ListenerRetriever caching -> no synchronization necessary
		return retrieveApplicationListeners(eventType, sourceType, null);
	}
}

繼續根據 retrieveApplicationListeners 方法,進行實際檢索給定事件和源類型的應用程序監聽器的實現。通過 supportsEvent 進行判斷監聽器是否支持給定事件,源碼如下:

private Collection<ApplicationListener<?>> retrieveApplicationListeners(
		ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {

	List<ApplicationListener<?>> allListeners = new ArrayList<>();
	//當前應用中的監聽器集合
	Set<ApplicationListener<?>> listeners;
	//當前應用中的監聽器BeanName集合
	Set<String> listenerBeans;
	synchronized (this.retrievalMutex) {
		listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
		listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
	}
	for (ApplicationListener<?> listener : listeners) {
		// 判斷監聽器是否支持給定事件
		if (supportsEvent(listener, eventType, sourceType)) {
			if (retriever != null) {
				retriever.applicationListeners.add(listener);
			}
			allListeners.add(listener);
		}
	}
	if (!listenerBeans.isEmpty()) {
		BeanFactory beanFactory = getBeanFactory();
		for (String listenerBeanName : listenerBeans) {
			try {
				//根據監聽器的BeanName獲取到對應類型
				Class<?> listenerType = beanFactory.getType(listenerBeanName);
				if (listenerType == null || supportsEvent(listenerType, eventType)) {
					ApplicationListener<?> listener =
							beanFactory.getBean(listenerBeanName, ApplicationListener.class);
					//判斷監聽器是否支持給定事件
					if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
						if (retriever != null) {
							if (beanFactory.isSingleton(listenerBeanName)) {
								retriever.applicationListeners.add(listener);
							}
							else {
								retriever.applicationListenerBeans.add(listenerBeanName);
							}
						}
						allListeners.add(listener);
					}
				}
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Singleton listener instance (without backing bean definition) disappeared -
				// probably in the middle of the destruction phase
			}
		}
	}
	AnnotationAwareOrderComparator.sort(allListeners);
	if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
		retriever.applicationListeners.clear();
		retriever.applicationListeners.addAll(allListeners);
	}
	return allListeners;
}

總結

我們已經完成對 Spring 事件機制原理的解析,主要流程如下:

  1. 初始化事件多播器
  2. 事件發佈器發佈事件
  3. 事件多播器接收到事件後,檢索監聽器集合得到需要執行事件處理的監聽器
  4. 循環調用具體監聽器方法處理事件

Spring 提供了以下 5 中標準的事件,我們可以註冊響應的監聽器進行處理該事件。

  1. 上下文更新事件(ContextRefreshedEvent):在調用ConfigurableApplicationContext 接口中的refresh()方法時被觸發。
  2. 上下文開始事件(ContextStartedEvent):當容器調用ConfigurableApplicationContext的Start()方法開始/重新開始容器時觸發該事件。
  3. 上下文停止事件(ContextStoppedEvent):當容器調用ConfigurableApplicationContext的Stop()方法停止容器時觸發該事件。
  4. 上下文關閉事件(ContextClosedEvent):當ApplicationContext被關閉時觸發該事件。容器被關閉時,其管理的所有單例Bean都被銷燬。
  5. 請求處理事件(RequestHandledEvent):在Web應用中,當一個http請求(request)結束觸發該事件。

參考:
https://www.jianshu.com/p/dcbe8f0afbdb

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章