天天看點

Spring事件執行流程源碼分析

1. 背景

為啥突然想到寫這個?起因就是看到了Nacos的

#3757 ISSUE

,了解錯誤, 以為是服務啟動,沒有注冊上服務,實際namespace不同,導緻服務無法注冊。 但這絲毫不影響再去研究了一波代碼,順便也看到了Nacos是如何利用Spring的事件來進行服務注冊的。分享一波,歡迎大家學習指正!

2. 娓娓道來 - Spring事件

2.1 怎麼釋出事件

我們大家應該都知道,在Spring中,是通過實作

org.springframework.context.ApplicationEventPublisher

來釋出一個事件。

ApplicationEcentPublisher

是一個接口,我們來看一下接口中邏輯。

public interface ApplicationEventPublisher {

	/**
	 * Notify all <strong>matching</strong> listeners registered with this
	 * application of an application event. Events may be framework events
	 * (such as RequestHandledEvent) or application-specific events.
	 * @param event the event to publish
	 * @see org.springframework.web.context.support.RequestHandledEvent
	 */
	default void publishEvent(ApplicationEvent event) {
		publishEvent((Object) event);
	}

	/**
	 * Notify all <strong>matching</strong> listeners registered with this
	 * application of an event.
	 * <p>If the specified {@code event} is not an {@link ApplicationEvent},
	 * it is wrapped in a {@link PayloadApplicationEvent}.
	 * @param event the event to publish
	 * @since 4.2
	 * @see PayloadApplicationEvent
	 */
	void publishEvent(Object event);
 }
           

接口很簡單,裡面有兩個方法,都是釋出事件,而接口預設實作,是調用

publishEvent(Object event)

的實作,唯一的差別就是default方法的參數是具體的事件類。

既然這是個接口,那一定有實作類,那麼我們肯定是要進入實作類,快捷鍵,檢視一下具體實作類。

Spring事件執行流程源碼分析

我們通過實作類,可以看到,應該是從

AbstractApplicationContext

進去(其他Context都是實作類,憑感覺和代碼提示,從第一個類進如代碼)。

進去

org.springframework.context.support.AbstractApplicationContext

,我們直奔主題,檢視是如何實作PublishEvent。具體代碼如下:

/**
	 * Publish the given event to all listeners.
	 * @param event the event to publish (may be an {@link ApplicationEvent}
	 * or a payload object to be turned into a {@link PayloadApplicationEvent})
	 * @param eventType the resolved event type, if known
	 * @since 4.2
	 */
	protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");

		// Decorate event as an ApplicationEvent if necessary
		ApplicationEvent applicationEvent;
		if (event instanceof ApplicationEvent) {
                        // 實作的具體的ApplicationEvent, 直接進行轉化即可
			applicationEvent = (ApplicationEvent) event;
		}
		else {
                        // 将事件裝飾成PayloadApplicationEvent,
			applicationEvent = new PayloadApplicationEvent<>(this, event);
			if (eventType == null) {
				eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
			}
		}

		// Multicast right now if possible - or lazily once the multicaster is initialized
		if (this.earlyApplicationEvents != null) {
                        // 廣播還未初始化完成,放入earlyApplicationEvents.
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
                        // 核心關注點: 使用廣播廣播事件,
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

		// Publish event via parent context as well...
                // 父容器的監聽器進行廣播
		if (this.parent != null) {
			if (this.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			}
			else {
				this.parent.publishEvent(event);
			}
		}
	}
           

在這段代碼中,我們得到了兩點有用的資訊,分别是:

  1. 釋出事件的整體流程。(封裝事件,廣播事件)
  2. 釋出的時間通過

    getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

    廣播事件

通過代碼可以知道,我們接下來的關注點是

getApplicationEventMulticaster()

,如何通過

multicastEvent()

方法将事件廣播出去呢?

2.2 什麼是ApplicationEventMulticaster

進入到核心關注點代碼,

getApplicationEventMulticaster()

是什麼?

ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
  // ...略...  沒什麼重要資訊
  return this.applicationEventMulticaster;
}
           

代碼很簡單,沒什麼邏輯,但是讓我們明白了

getApplicationEventMulticaster()

擷取到的對象是

ApplicationEventMulticaster

,那我們接下來搞懂這個東西(

ApplicationEventMulticaster

)是如何初始化?理清楚後可以弄清楚邏輯了。

輕輕松松翻到

ApplicationEventMulticaster

初始化的代碼(給自己鼓個掌👏),代碼如下:

/**
 * Initialize the ApplicationEventMulticaster.
 * Uses SimpleApplicationEventMulticaster if none defined in the context.
 * @see org.springframework.context.event.SimpleApplicationEventMulticaster
 */
protected void initApplicationEventMulticaster() {
  ConfigurableListableBeanFactory beanFactory = getBeanFactory();
  // 判斷容器中是否已經建立ApplicationEventMulticaster
  if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
    this.applicationEventMulticaster =
      beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
  }
  else {
    // 初始化預設ApplicationEventMulticaster
    this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
    beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
  }
}
           

代碼很簡單,就是判斷容器中是否已經包含了

ApplicationEventMulticaster

(可以了解為是否自己實作了

ApplicationEventMulticaster

),如果沒實作,則采用Spring的預設實作

SimpleApplicationEventMulticaster

到目前為止,我們已經知道的資訊是:

  1. SimpleApplicationEventMulticaster

    ApplicationEventMulticaster

    的預設實作

搞到了這個有用的資訊,那麼我們去搞懂

SimpleApplicationEventMulticaster

是什麼就可以知道Spring的廣播流程了

2.2.1 SimpleApplicationEventMulticaster 是什麼?如何廣播事件?

帶着思考與疑惑,我們進入

SimpleApplicationEventMulticaster

,就看看

multicastEvent()

方法, 因為上面代碼中有寫到,發送事件是這樣操作的:

getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

方法如下:

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);
    }
  }
}
           

代碼看到這, 我們已經有了大概的了解,但大家應該還有兩個疑問,分别是:

  1. getApplicationListeners(event, type)

    是什麼?(這個很重要,包含著為什麼特定的Listener能接收到特定的事件)
  2. invokeListener(listener, event);

    裡面的邏輯是什麼?

在這兩個問題中,第一個問題我們暫時先放下,因為這暫時不涉及事件調用流程,後邊說。我們就先說一下第二個以為。

invokeListener(listener, event);

邏輯是什麼。

我們帶着疑惑,接着進入代碼:

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
  try {
    // 監聽器處理事件
    listener.onApplicationEvent(event);
  }
  catch (ClassCastException ex) {
    ...略...
  }
}
           

到這,知道了。邏輯蠻簡單的,就是調用Listener處理事件的邏輯。我們腦海中的有個大概流程圖了,如下圖所示:

Spring事件執行流程源碼分析

通過

AbstractApplicationContext實作類

釋出事件,在通過

SimpleApplicationEventMulticaster

将事件廣播到所有Listener,就完成了整個過程的調用。但是,我們還有其中二塊沒有搞清楚,分别是:

  1. allListener是怎麼來的呢?
  2. 為什麼能根據事件類型來決定調用哪些Listener?

2.3 Listener的秘密

代碼疑問,回到代碼。從

multicastEvent()

方法中的

getApplicationListeners(event, type)

進入,發現進入到了

AbstractApplicationEventMulticaster

代碼中,發現了Listener的增減和删除邏輯(為什麼先看這兩個?類的代碼不多,可以先随便瞅瞅,不要太局限)。增加代碼邏輯如下:

// 通過實作類添加listener
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();
  }
}
// 通過beanName添加listener
public void addApplicationListenerBean(String listenerBeanName) {
  synchronized (this.retrievalMutex) {
    this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
    this.retrieverCache.clear();
  }
}
           

從參數可以看出,兩個方法的差一點,就不細說。但是我們好奇啊,代碼中的

defaultRetriever

是什麼,有屬性?為什麼listener儲存在這個裡面?還是帶着疑惑,進入代碼,核心代碼如下:

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);
        // 是否需要過濾重複的listener
        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
      }
    }
  }
  if (!this.preFiltered || !this.applicationListenerBeans.isEmpty()) {
    AnnotationAwareOrderComparator.sort(allListeners);
  }
  return allListeners;
}
           

代碼邏輯蠻簡單的,就是講beanName方式的listener和listener實作類直接結合放在一個集合中,并且進行排序傳回。

接着,我們在來看

getApplicationListeners(event, type)

的具體實作:

/**
 * Return a Collection of ApplicationListeners matching the given
 * event type. Non-matching listeners get excluded early.
 * @param event the event to be propagated. Allows for excluding
 * non-matching listeners early, based on cached matching information.
 * @param eventType the event type
 * @return a Collection of ApplicationListeners
 * @see org.springframework.context.ApplicationListener
 */
protected Collection<ApplicationListener<?>> getApplicationListeners(
  ApplicationEvent event, ResolvableType eventType) {

  Object source = event.getSource();
  Class<?> sourceType = (source != null ? source.getClass() : null);
  // 通過事件類型和事件源類型建構緩存key
  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)))) {
   ... 删掉了複雜邏輯,直接看下面else代碼
  }
  else {
    // No ListenerRetriever caching -> no synchronization necessary
    // 如何區分事件的執行邏輯
    return retrieveApplicationListeners(eventType, sourceType, null);
  }
}
           

通過上面代碼,我們發現,spring是在本地對事件有緩存,通過事件類型和事件源類型建構ListenerCacheKey。 但是還沒有到核心點, 如何區分事件的呢?接着我們進入

retrieveApplicationListeners(eventType, sourceType, null);

中去尋找事情的真正原因。

/**
 * Actually retrieve the application listeners for the given event and source type.
 * @param eventType the event type
 * @param sourceType the event source type
 * @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes)
 * @return the pre-filtered list of application listeners for the given event and source type
 */
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
  ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {

  List<ApplicationListener<?>> allListeners = new ArrayList<>();
  Set<ApplicationListener<?>> listeners;
  Set<String> listenerBeans;
  synchronized (this.retrievalMutex) {
    listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
    listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
  }
  // 已經加裝的Listener
  for (ApplicationListener<?> listener : listeners) {
    if (supportsEvent(listener, eventType, sourceType)) {
      if (retriever != null) {
        retriever.applicationListeners.add(listener);
      }
      allListeners.add(listener);
    }
  }
  // beanName的listener
  if (!listenerBeans.isEmpty()) {
    BeanFactory beanFactory = getBeanFactory();
    for (String listenerBeanName : listenerBeans) {
      try {
        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;
}
           

代碼很簡單,就是把beanName的Listener和listener實作類拿出去,找出支援eventType和sourceType的。 自此,我們也弄懂了,是如何添加listener的,大功告成!!!

别捉急,我們得把上面看的代碼在整理一下,把流程圖完善一下,如下圖所示:

Spring事件執行流程源碼分析

3 大功告成

至此,分析完了整個流程,弄懂了Spring整個事件廣播流程,是不是特别簡單?!!!!, 沒搞懂的時候覺得難,仔細看一下發現如此簡單哇! 給自己加個油,可以的!

4. Spring中的初始化流程與Nacos中的使用

這部分知識還有點多,暫時先不寫,先放在下篇文章中,下篇文章将具體說一下我的分析ISSUE的流程,以及我的發現-----Nacos中如何使用事件來将服務注冊到Nacos Discover中的。 還有一個就是Spring初始化流程(這個初始化流程也包含了Nacos注冊的一個小問題:當服務不是web服務的時候,Nacos将不會注冊這個服務,不知道算不算問題? 下次在接着說)。

5. 本文為個人分析,如果有問題歡迎指出, 謝謝啦!

你的每一個點贊,我都當做喜歡