springboot啓動-監聽器模塊

監聽器模塊簡介

  1. springboot在啓動過程中會調用監聽器模塊,將開始事件、環境準備事件、啓動完成/失敗、準備完成等事件發佈出去,客戶端可以監聽各種類型的事件進行特殊處理。

工作流程

  1. 服務端發佈消息入口:SpringApplicationRunListeners,遍歷調用SpringApplicationRunListener接口列表
  2. 中間服務商:SpringApplicationRunListener,提供發佈消息的接口方法,底層調用ApplicationEventMulticaster的廣播接口發佈消息
  3. 底層維護者:ApplicationEventMulticaster,維護客戶端訂閱的監聽實例,可以添加、刪除監聽器,可以將消息廣播給對應的監聽器
  4. 客戶端訂閱:ApplicationListener,提供給客戶端實現接口

源碼分析

  1. 監聽器模塊主要分爲幾個核心接口:
    SpringApplicationRunListeners->持有服務端SpringApplicationRunListener列表
    SpringApplicationRunListener->服務端調用監聽接口
    ApplicationListener->提供給客戶端訂閱的監聽接口
    ApplicationEventMulticaster->事件廣播接口,負責維護客戶端訂閱的監聽接口列表
  2. 我們常監聽的springboot事件如下(有序):
    ApplicationStartingEvent
    ApplicationEnvironmentPreparedEvent
    ApplicationFailedEvent
    ApplicationStartedEvent
    ApplicationReadyEvent

SpringApplicationRunListeners

  1. 這是一個入口類,在SpringApplication的run方法中進行調度,下面我們看下SpringApplicationRunListeners的相關源碼:
屬性:SpringApplicationRunListener集合是springboot啓動時從spring.factory配置文件中讀取
private final List<SpringApplicationRunListener> listeners;
構造器:
SpringApplicationRunListeners(Log log,
		Collection<? extends SpringApplicationRunListener> listeners) {
	this.log = log;
	// 外部傳入SpringApplicationRunListener實例列表
	this.listeners = new ArrayList<>(listeners);
}
核心方法:
public void starting() {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.starting();
	}
}

public void environmentPrepared(ConfigurableEnvironment environment) {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.environmentPrepared(environment);
	}
}

public void contextPrepared(ConfigurableApplicationContext context) {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.contextPrepared(context);
	}
}

public void contextLoaded(ConfigurableApplicationContext context) {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.contextLoaded(context);
	}
}

public void started(ConfigurableApplicationContext context) {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.started(context);
	}
}

public void running(ConfigurableApplicationContext context) {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.running(context);
	}
}

public void failed(ConfigurableApplicationContext context, Throwable exception) {
	for (SpringApplicationRunListener listener : this.listeners) {
		callFailedListener(listener, context, exception);
	}
}

SpringApplicationRunListener

實現類:EventPublishingRunListener

  1. EventPublishingRunListener目前是SpringApplicationRunListener接口的唯一實現類,並通過spring.factory進行讀取類路徑配置,進行實例化
  2. EventPublishingRunListener內部封裝了調用ApplicationEventMulticaster接口的邏輯,實現消息的發佈
屬性:
private final SpringApplication application;
private final SimpleApplicationEventMulticaster initialMulticaster;
構造器:
public EventPublishingRunListener(SpringApplication application, String[] args) {
	this.application = application;
	this.args = args;
	this.initialMulticaster = new SimpleApplicationEventMulticaster();
	// 將客戶端監聽器加入SimpleApplicationEventMulticaster實例中進行管理
	for (ApplicationListener<?> listener : application.getListeners()) {
		this.initialMulticaster.addApplicationListener(listener);
	}
}
核心方法:
@Override
public void starting() {
	this.initialMulticaster.multicastEvent(
			new ApplicationStartingEvent(this.application, this.args));
}

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
	this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
			this.application, this.args, environment));
}

…

ApplicationEventMulticaster

  1. 抽象實現類:ApplicationEventMulticaster,實現了客戶端監聽器添加、刪除的實現邏輯,內部實現了監聽器的本地緩存操作,這是比較複雜的一塊邏輯
  2. 實現類:SimpleApplicationEventMulticaster,實現了事件廣播的具體邏輯

AbstractApplicationEventMulticaster->本地緩存底層實現

屬性:
// ListenerCacheKey主要是重寫equals和hashcode方法,ListenerRetriever主要是持有客戶端監聽器實例以及容器中監聽器bean列表
final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
// 排他鎖,用於同步處理邏輯
private Object retrievalMutex = this.defaultRetriever;

首先看下ListenerCacheKey
// 實現了Comparable接口,說明想支持排序功能
private static final class ListenerCacheKey implements Comparable<ListenerCacheKey> {

	private final ResolvableType eventType;

	@Nullable
	private final Class<?> sourceType;

	public ListenerCacheKey(ResolvableType eventType, @Nullable Class<?> sourceType) {
		Assert.notNull(eventType, "Event type must not be null");
		this.eventType = eventType;
		this.sourceType = sourceType;
	}

	// 重寫equals方法,通過判斷事件類型以及類型是否一致來判斷key是否相同,避免同一個客戶端監聽器被加載到本地緩存多次
	@Override
	public boolean equals(Object other) {
		if (this == other) {
			return true;
		}
		ListenerCacheKey otherKey = (ListenerCacheKey) other;
		return (this.eventType.equals(otherKey.eventType) &&
				ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType));
	}
	
	// 重寫hashCode,一個對象要作爲Map的key,必須同時重寫equals方法和hashCode方法
	@Override
	public int hashCode() {
		return this.eventType.hashCode() * 29 + ObjectUtils.nullSafeHashCode(this.sourceType);
	}

	@Override
	public String toString() {
		return "ListenerCacheKey [eventType = " + this.eventType + ", sourceType = " + this.sourceType + "]";
	}

	// 實現compareTo接口,根據事件類型字符串長度來比較,得到先後順序
	@Override
	public int compareTo(ListenerCacheKey other) {
		int result = this.eventType.toString().compareTo(other.eventType.toString());
		if (result == 0) {
			if (this.sourceType == null) {
				return (other.sourceType == null ? 0 : -1);
			}
			if (other.sourceType == null) {
				return 1;
			}
			result = this.sourceType.getName().compareTo(other.sourceType.getName());
		}
		return result;
	}
}

再看下ListenerRetriever
private class ListenerRetriever {
	// 持有客戶端監聽器列表
	public final Set<ApplicationListener<?>> applicationListeners;
	// 支持從容器中根據bean名稱讀取監聽器,這裏應該是存儲spring管理的監聽器單例
	public final Set<String> applicationListenerBeans;

	private final boolean preFiltered;

	public ListenerRetriever(boolean preFiltered) {
		this.applicationListeners = new LinkedHashSet<>();
		this.applicationListenerBeans = new LinkedHashSet<>();
		this.preFiltered = preFiltered;
	}

	public Collection<ApplicationListener<?>> getApplicationListeners() {
		List<ApplicationListener<?>> allListeners = new ArrayList<>(
				this.applicationListeners.size() + this.applicationListenerBeans.size());
		allListeners.addAll(this.applicationListeners);
		if (!this.applicationListenerBeans.isEmpty()) {
			BeanFactory beanFactory = getBeanFactory();
			for (String listenerBeanName : this.applicationListenerBeans) {
				try {
					// 從容器中獲取監聽器
					ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
					if (this.preFiltered || !allListeners.contains(listener)) {
						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);
		return allListeners;
	}
}

AbstractApplicationEventMulticaster->監聽器增刪改查操作

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();
	}
	// 根據雙親委派策略,判斷是否是相同的類加載器加載的Bean,如果是則可以進行緩存策略
	if (this.beanClassLoader == null ||
			(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
					(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
		// 使用sychronized鎖全部,進行緩存更新操作
		// Fully synchronized building and caching of a 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);
	}
}

增刪改查監聽器:都進行加鎖操作,清除緩存數據
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
	synchronized (this.retrievalMutex) {
		// Explicitly remove target for a proxy, if registered already,
		// in order to avoid double invocations of the same listener.
		Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
		if (singletonTarget instanceof ApplicationListener) {
			this.defaultRetriever.applicationListeners.remove(singletonTarget);
		}
		this.defaultRetriever.applicationListeners.add(listener);
		this.retrieverCache.clear();
	}
}

@Override
public void addApplicationListenerBean(String listenerBeanName) {
	synchronized (this.retrievalMutex) {
		this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
		this.retrieverCache.clear();
	}
}

@Override
public void removeApplicationListener(ApplicationListener<?> listener) {
	synchronized (this.retrievalMutex) {
		this.defaultRetriever.applicationListeners.remove(listener);
		this.retrieverCache.clear();
	}
}

@Override
public void removeApplicationListenerBean(String listenerBeanName) {
	synchronized (this.retrievalMutex) {
		this.defaultRetriever.applicationListenerBeans.remove(listenerBeanName);
		this.retrieverCache.clear();
	}
}

@Override
public void removeAllListeners() {
	synchronized (this.retrievalMutex) {
		this.defaultRetriever.applicationListeners.clear();
		this.defaultRetriever.applicationListenerBeans.clear();
		this.retrieverCache.clear();
	}
}

protected Collection<ApplicationListener<?>> getApplicationListeners() {
	synchronized (this.retrievalMutex) {
		return this.defaultRetriever.getApplicationListeners();
	}
}

SimpleApplicationEventMulticaster->發佈消息

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	// 根據事件類型去獲取對應的監聽器列表
	for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		// 判斷是否支持異步
		Executor executor = getTaskExecutor();
		if (executor != null) {
			executor.execute(() -> invokeListener(listener, event));
		}
		else {
			invokeListener(listener, event);
		}
	}
}

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
	ErrorHandler errorHandler = getErrorHandler();
	// 如果發送異常,是否走錯誤處理策略,目前這裏默認的錯誤處理策略是:LoggingErrorHandler,即打印“Error calling ApplicationEventListener xxx”日誌
	if (errorHandler != null) {
		try {
			doInvokeListener(listener, event);
		}
		catch (Throwable err) {
			errorHandler.handleError(err);
		}
	}
	else {
		doInvokeListener(listener, event);
	}
}

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
	try {
		// 看到熟悉的onApplicationEvent方法了,這個就是我們客戶端監聽器實現的方法,將消息通知給客戶端
		listener.onApplicationEvent(event);
	}
	catch (ClassCastException ex) {
		String msg = ex.getMessage();
		// 判斷導致ClassCastException異常原因是否是event.getClass()類型轉換異常
		if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
			// Possibly a lambda-defined listener which we could not resolve the generic event type for
			// -> let's suppress the exception and just log a debug message.
			
			Log logger = LogFactory.getLog(getClass());
			if (logger.isDebugEnabled()) {
				logger.debug("Non-matching event type for listener: " + listener, ex);
			}
		}
		else {
			throw ex;
		}
	}
}

== 比較有意思的一個地方:doInvokeListener方法中catch部分==

  1. 這裏可能會出現ClassCastException異常:出現這個異常很可能的原因是這個監聽器是通過lamda方式定義的,lamda定義的監聽器會監聽到所有的消息類型;
  2. 我做了個測試:application.addListeners((ApplicationEnvironmentPreparedEvent event) -> System.out.println("123”)),啓動springboot應用,發現確實會觸發這個異常,那麼爲什麼會出現這個異常呢?
  3. 原因分析:問題出在獲取監聽器列表的方法上,不管根據哪種事件類型來獲取監聽器列表,都會把lamda方式創建的監聽器查到,進行代碼分析,發現該監聽器對每種事件類型都支持監聽:
getApplicationListeners->retrieveApplicationListeners->supportsEvent

protected boolean supportsEvent(
		ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
	
	GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
			(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
	// 這裏smartListener是GenericApplicationListenerAdapter實例
	// 這裏判斷邏輯對於lamda監聽器始終爲true,原因是lamda表達式沒法識別具體類型啊,所以把消息類型識別成最父級的類型了,即ApplicationEvent,所以支持所有類型的消息
	return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

ApplicationListener

  1. 客戶端可以自己實現監聽器接口,從而對spring相關事件進行監聽,可以參考我之前寫的一篇關於監聽器實現的文章:SpringBoot-事件監聽的4種實現方式
  2. 接口定義如下:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);

}

設計總結

  1. 監聽器模塊主要使用了:策略模式、模版方法模式、觀察者模式
  2. 使用了本地緩存策略,並保證註冊監聽器列表更新時可以實時清除緩存,使用鎖機制保證操作安全性

經驗總結

  1. 最好不要用lamda方式創建監聽器(雖然可以實現監聽功能),最好顯示定義監聽器

擴展學習

  1. 對監聽器模塊源碼學習了之後,我嘗試自己寫了一個類似的監聽器模塊,更新在github上springboot基於底層代碼的一些學習
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章