springboot啓動-監聽器模塊
監聽器模塊簡介
- springboot在啓動過程中會調用監聽器模塊,將開始事件、環境準備事件、啓動完成/失敗、準備完成等事件發佈出去,客戶端可以監聽各種類型的事件進行特殊處理。
工作流程
- 服務端發佈消息入口:SpringApplicationRunListeners,遍歷調用SpringApplicationRunListener接口列表
- 中間服務商:SpringApplicationRunListener,提供發佈消息的接口方法,底層調用ApplicationEventMulticaster的廣播接口發佈消息
- 底層維護者:ApplicationEventMulticaster,維護客戶端訂閱的監聽實例,可以添加、刪除監聽器,可以將消息廣播給對應的監聽器
- 客戶端訂閱:ApplicationListener,提供給客戶端實現接口
源碼分析
- 監聽器模塊主要分爲幾個核心接口:
SpringApplicationRunListeners->持有服務端SpringApplicationRunListener列表
SpringApplicationRunListener->服務端調用監聽接口
ApplicationListener->提供給客戶端訂閱的監聽接口
ApplicationEventMulticaster->事件廣播接口,負責維護客戶端訂閱的監聽接口列表 - 我們常監聽的springboot事件如下(有序):
ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent
ApplicationFailedEvent
ApplicationStartedEvent
ApplicationReadyEvent
SpringApplicationRunListeners
- 這是一個入口類,在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
- EventPublishingRunListener目前是SpringApplicationRunListener接口的唯一實現類,並通過spring.factory進行讀取類路徑配置,進行實例化
- 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
- 抽象實現類:ApplicationEventMulticaster,實現了客戶端監聽器添加、刪除的實現邏輯,內部實現了監聽器的本地緩存操作,這是比較複雜的一塊邏輯
- 實現類: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部分==
- 這裏可能會出現ClassCastException異常:出現這個異常很可能的原因是這個監聽器是通過lamda方式定義的,lamda定義的監聽器會監聽到所有的消息類型;
- 我做了個測試:application.addListeners((ApplicationEnvironmentPreparedEvent event) -> System.out.println("123”)),啓動springboot應用,發現確實會觸發這個異常,那麼爲什麼會出現這個異常呢?
- 原因分析:問題出在獲取監聽器列表的方法上,不管根據哪種事件類型來獲取監聽器列表,都會把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
- 客戶端可以自己實現監聽器接口,從而對spring相關事件進行監聽,可以參考我之前寫的一篇關於監聽器實現的文章:SpringBoot-事件監聽的4種實現方式
- 接口定義如下:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
設計總結
- 監聽器模塊主要使用了:策略模式、模版方法模式、觀察者模式
- 使用了本地緩存策略,並保證註冊監聽器列表更新時可以實時清除緩存,使用鎖機制保證操作安全性
經驗總結
- 最好不要用lamda方式創建監聽器(雖然可以實現監聽功能),最好顯示定義監聽器
擴展學習
- 對監聽器模塊源碼學習了之後,我嘗試自己寫了一個類似的監聽器模塊,更新在github上springboot基於底層代碼的一些學習