深入理解 Spring 的事件發布監聽機制

1. 什么是事件監聽機制

在講解事件監聽機制前,我們先回顧下設計模式中的觀察者模式,因為事件監聽機制可以說是在典型觀察者模式基礎上的進一步抽象和改進。我們可以在 JDK 或者各種開源框架比如 Spring 中看到它的身影,從這個意義上說,事件監聽機制也可以看做一種對傳統觀察者模式的具體實現,不同的框架對其實現方式會有些許差別。

典型的觀察者模式將有依賴關系的對象抽象為了觀察者主題兩個不同的角色,多個觀察者同時觀察一個主題,兩者只通過抽象接口保持松耦合狀態,這樣雙方可以相對獨立的進行擴展和變化:比如可以很方便的增刪觀察者,修改觀察者中的更新邏輯而不用修改主題中的代碼。但是這種解耦進行的并不徹底,這具體體現在以下幾個方面:

  • 1.抽象主題需要依賴抽象觀察者,而這種依賴關系完全可以去除。
  • 2.主題需要維護觀察者列表,并對外提供動態增刪觀察者的接口,
  • 3.主題狀態改變時需要由自己去通知觀察者進行更新。

我們可以把主題(Subject)替換成事件(Event),把對特定主題進行觀察的觀察者(Observer)替換成對特定事件進行監聽的監聽器(EventListener),而把原有主題中負責維護主題與觀察者映射關系以及在自身狀態改變時通知觀察者的職責從中抽出,放入一個新的角色事件發布器(EventPublisher)中,事件監聽模式的輪廓就展現在了我們眼前,如下圖所示:



常見事件監聽機制的主要角色如下:

  • 事件:對應于觀察者模式中的主題。
  • 事件監聽器:對應于觀察者模式中的觀察者。監聽器監聽特定事件,并在內部定義了事件發生后的響應邏輯。
  • 事件發布器:事件監聽器的容器,對外提供發布事件和增刪事件監聽器的接口,維護事件和事件監聽器之間的映射關系,并在事件發生時負責通知相關監聽器。

Spring 框架對事件的發布與監聽提供了相對完整的支持,它擴展了JDK中對自定義事件監聽提供的基礎框架,并與 Spring 的 IOC 特性作了整合,使得用戶可以根據自己的業務特點進行相關的自定義,并依托 Spring 容器方便的實現監聽器的注冊和事件的發布。

2. Spring 容器對事件監聽機制的支持

Spring 容器,具體而言是ApplicationContext接口定義的容器提供了一套相對完善的事件發布和監聽框架,其遵循了JDK 中的事件監聽標準,并使用容器來管理相關組件,使得用戶不用關心事件發布和監聽的具體細節,降低了開發難度也簡化了開發流程。下面看看對于事件監聽機制中的各主要角色,Spring 框架中是如何定義的,以及相關的類體系結構:

  • 事件
    Spring 為容器內事件定義了一個抽象類ApplicationEvent,該類繼承了JDK 中的事件基類EventObject。因而自定義容器內事件除了需要繼承ApplicationEvent之外,還要傳入事件源作為構造參數:
public abstract class ApplicationEvent extends EventObject {

    /** use serialVersionUID from Spring 1.2 for interoperability */
    private static final long serialVersionUID = 7099057708183571937L;

    /** System time when the event happened */
    private final long timestamp;


    /**
     * Create a new ApplicationEvent.
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }


    /**
     * Return the system time in milliseconds when the event happened.
     */
    public final long getTimestamp() {
        return this.timestamp;
    }

}
  • 事件監聽器
    Spring 定義了一個ApplicationListener接口作為事件監聽器的抽象,接口定義如下:
@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. 該接口繼承了 JDK 中表示事件監聽器的標記接口EventListener,內部只定義了一個抽象方法onApplicationEvent(evnt),當監聽的事件在容器中被發布,該方法將被調用。
  2. 同時,該接口是一個泛型接口,其實現類可以通過傳入泛型參數指定該事件監聽器要對哪些事件進行監聽。這樣有什么好處?這樣所有的事件監聽器就可以由一個事件發布器進行管理,并對所有事件進行統一發布,而具體的事件和事件監聽器之間的映射關系,則可以通過反射讀取泛型參數類型的方式進行匹配,稍后我們會對原理進行講解。
  3. 最后,所有的事件監聽器都必須向容器注冊,容器能夠對其進行識別并委托容器內真正的事件發布器進行管理。
  • 事件發布器
    ApplicationContext接口繼承了ApplicationEventPublisher接口,從而提供了對外發布事件的能力。
    那么是否可以說ApplicationContext即容器本身就擔當了事件發布器的角色呢?其實這是不準確的,容器本身僅僅是對外提供了事件發布的接口,真正的工作其實是委托給了具體容器內部一個ApplicationEventMulticaster對象,其定義在AbstractApplicationContext抽象類內部,如下所示:
public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    ...
    /** Helper class used in event publishing */
    @Nullable
    private ApplicationEventMulticaster applicationEventMulticaster;
    ...
}

所以,真正的事件發布器是ApplicationEventMulticaster,這是一個接口,定義了事件發布器需要具備的基本功能:管理事件監聽器以及發布事件。其默認實現類是
SimpleApplicationEventMulticaster,該組件會在容器啟動時被自動創建,并以單例的形式存在,管理了所有的事件監聽器,并提供針對所有容器內事件的發布功能。

3. 基于 Spring 實現自定義事件與監聽

想象我們正在做一個任務調度系統,我們需要把任務提交到集群中并監控任務的執行狀態,當任務執行完畢(成功或者失敗),除了必須對數據庫進行更新外,還可以執行一些額外的工作:比如將任務執行結果以郵件的形式發送給用戶。這些額外的工作后期還有較大的變動可能:比如還需要以短信的形式通知用戶,對于特定的失敗任務需要通知相關運維人員進行排查等等,所以不宜直接寫死在主流程代碼中。最好的方式自然是以事件監聽的方式動態的增刪對于任務執行結果的處理邏輯。為此我們可以基于 Spring 實現自定義事件與監聽,打造一個能夠對任務執行結果進行監聽的彈性系統。

  • 自定任務結束事件
    定義一個任務結束事件TaskFinishedEvent,該類繼承抽象類ApplicationEvent來遵循容器事件規范。
package com.tongbanjie.application.event;

import org.springframework.context.ApplicationEvent;

/**
 * @author Lu. Yan
 * @create 2019-06-26
 */
public class TaskFinishedEvent extends ApplicationEvent {
    
    /**
     * Create a new ApplicationEvent.
     *
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public TaskFinishedEvent(Object source) {
        super(source);
    }
}
  • 自定義郵件服務監聽器并向容器注冊
    該類實現了容器事件規范定義的監聽器接口ApplicationListener<?>,通過泛型參數指定對上面定義的任務結束事件進行監聽,通過 @Component 注解向容器進行注冊。
package com.tongbanjie.application.listener;

import com.tongbanjie.application.event.TaskFinishedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @author Lu. Yan
 * @create 2019-06-26
 */
@Component
public class MailTaskFinishedListener implements ApplicationListener<TaskFinishedEvent> {

    private String emailAddr ="xxxxxx@163.com";

    @Override
    public void onApplicationEvent(TaskFinishedEvent event) {

        System.out.println("Send Email to "+ emailAddr +" Task:"+event.getSource());
    }
}
  • 發布事件
    從上面對 Spring 事件監聽機制的類結構分析可知,發布事件的功能定義在ApplicationEventPublisher接口中,而ApplicationContext繼承了該接口,所以最好的方法是通過實現ApplicationContextAware接口獲取ApplicationContext實例,然后調用其發布事件方法。
@Component
public class EventPublisher implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    //發布事件
    public void publishEvent(ApplicationEvent event){

        applicationContext.publishEvent(event);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }
}

這樣就可以在任務結束之后,調用EventPublisher#publishEvent(ApplicationEvent)方法,來發布TaskFinishedEvent事件。

4. Spring 事件監聽源碼解析

Spring 事件監聽機制離不開容器 IOC 特性提供的支持,比如容器會自動創建事件發布器,自動識別用戶注冊的監聽器并進行管理,在特定的事件發布后會找到對應的事件監聽器并對其監聽方法進行回調。Spring 幫助用戶屏蔽了關于事件監聽機制背后的很多細節,使用戶可以專注于業務層面進行自定義事件開發。然而我們還是忍不住對其背后的實現原理進行一番探討,比如:

  1. 事件發布器ApplicationEventMulticaster是何時被初始化的,初始化過程中都做了什么?
  2. 注冊事件監聽器的過程是怎樣的,容器怎么識別出它們并進行管理?
  3. 容器發布事件的流程是怎樣的?它如何根據發布的事件找到對應的事件監聽器,事件和由該事件觸發的監聽器之間的匹配規則是怎樣的?

為了對以上問題進行解答,我們不得不深入源碼層面一探究竟。

4.1 初始化事件發布器流程

真正的事件發布器是ApplicationEventMulticaster,它定義在AbstractApplicationContext中,并在ApplicationContext容器啟動的時候進行初始化。在容器啟動的refrsh()方法中可以找到初始化事件發布器的入口方法,如下圖所示:

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    ...
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

    /**
     * Initialize the ApplicationEventMulticaster.
     * Uses SimpleApplicationEventMulticaster if none defined in the context.
     * @see org.springframework.context.event.SimpleApplicationEventMulticaster
     */
    protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        // 判斷beanFactory里是否定義了id為applicationEventMulticaster的bean,默認是沒有的
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            this.applicationEventMulticaster =
                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
            }
        }
        else {
            //一般情況會走這里,創建一個SimpleApplicationEventMulticaster并交由容器管理
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
            if (logger.isDebugEnabled()) {
                logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
                        APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
                        "': using default [" + this.applicationEventMulticaster + "]");
            }
        }
    }

}

initApplicationEventMulticaster()方法會初始化ApplicationEventMulticasterSimpleApplicationEventMulticaster的實例。之后會調用beanFactory.registerSingleton方法將創建的SimpleApplicationEventMulticaster實例注冊為容器的單實例 bean。

初始化事件發布器的工作非常簡單,一句話總結:由容器實例化用戶自定義的事件發布器或者由容器幫我們創建一個簡單的事件發布器并交由容器管理。

4.2 注冊事件監聽器流程

注冊事件監聽器的流程在初始化事件發布器之后,在registerListeners()方法中:

    protected void registerListeners() {
        // Register statically specified listeners first.
        for (ApplicationListener<?> listener : getApplicationListeners()) {
            getApplicationEventMulticaster().addApplicationListener(listener);
        }

        //獲取實現ApplicationListener接口的所有bean的beanNamegetBeanNamesForType(ApplicationListener.class, true, false);
        for (String listenerBeanName : listenerBeanNames) {
            //將監聽器的beanName保存到事件發布器中
            getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
        }

        // Publish early application events now that we finally have a multicaster...
        Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
        this.earlyApplicationEvents = null;
        if (earlyEventsToProcess != null) {
            for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
                getApplicationEventMulticaster().multicastEvent(earlyEvent);
            }
        }
    }

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

主要流程為:

  1. 遍歷AbstractApplicationContext#applicationListeners的屬性,其存放的是事件監聽器ApplicationListener集合。將applicationListeners集合存入其關聯的事件發布器SimpleApplicationEventMulticaster.ListenerRetriever#applicationListeners屬性中。
  2. 遍歷beanFactory中所有的 bean,獲取所有實現ApplicationListener接口的 bean 的 beanName,并將這些 beanName 注冊到其關聯的事件發布器SimpleApplicationEventMulticaster.ListenerRetriever#applicationListenerBeans屬性中。
  3. 如果有earlyApplicationEvents,則先發布這些事件。

defaultRetriever是定義在抽象類AbstractApplicationEventMulticaster中的成員,用來保存所有事件監聽器及其 beanName。

public abstract class AbstractApplicationEventMulticaster
        implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {

    private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);
}

private class ListenerRetriever {

        //保存所有事件監聽器
        public final Set<ApplicationListener<?>> applicationListeners;

        //保存所有事件監聽器的beanName
        public final Set<String> applicationListenerBeans;

其實看到這里會有一個疑問,registerListeners()方法只是找到了所有監聽器的 beanName 并將其保存到了事件發布器ApplicationEventMulticaster中,那么真正的事件監聽器實例是何時被創建并被加入到事件發布器中的?

這里我們不得不退回到啟動容器的refresh()方法中,在初始化 beanFactory 之后,初始化事件發布器之前,容器在prepareBeanFactory(beanFactory)方法中又注冊了一些重要組件,其中就包括一個特殊的BeanPostProcessor:ApplicationListenerDetector,正如其類名暗示的那樣,這是一個事件監聽器的探測器。

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // Tell the internal bean factory to use the context's class loader etc.
        beanFactory.setBeanClassLoader(getClassLoader());
        beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
        beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
        ...
        // Register early post-processor for detecting inner beans as ApplicationListeners.
        beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

        // Detect a LoadTimeWeaver and prepare for weaving, if found.
        if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            // Set a temporary ClassLoader for type matching.
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }

        // Register default environment beans.
        if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
        }
    }

該類實現了BeanPostProcessor接口,如下圖所示:

ApplicationListenerDetector實現了BeanPostProcessor接口,可以在容器級別對所有 bean 的生命周期過程進行增強。這里主要是為了能夠在初始化所有 bean 后識別出所有的事件監聽器 bean 并將其注冊到事件發布器中。

    public Object postProcessAfterInitialization(Object bean, String beanName) {
      //判斷該bean是否實現了ApplicationListener接口       
      if (this.applicationContext != null && bean instanceof ApplicationListener) {
            // potentially not detected as a listener by getBeanNamesForType retrieval
            Boolean flag = this.singletonNames.get(beanName);
            if (Boolean.TRUE.equals(flag)) {
                // singleton bean (top-level or inner): register on the fly
                //將實現了ApplicationListener接口的bean注冊到事件發布器applicationEventMulticaster中
                this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
            }
            else if (Boolean.FALSE.equals(flag)) {
                if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
                    // inner bean with other scope - can't reliably process events
                    logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
                            "but is not reachable for event multicasting by its containing ApplicationContext " +
                            "because it does not have singleton scope. Only top-level listener beans are allowed " +
                            "to be of non-singleton scope.");
                }
                this.singletonNames.remove(beanName);
            }
        }
        return bean;
    }

在初始化所有的 bean 后,該ApplicationListenerDetectorpostProcessAfterInitialization(Object bean, String beanName)方法會被作用在每一個 bean 上,通過判斷傳入的 bean 是否是ApplicationListener實例進行過濾,然后將找到的事件監聽器 bean 注冊到事件發布器中。

4.3 容器事件發布流程

發布事件,調用AbstractApplicationContext#publishEvent(ApplicationEvent)方法:

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

  protected void publishEvent(Object event, ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");
        if (logger.isTraceEnabled()) {
            logger.trace("Publishing event in " + getDisplayName() + ": " + event);
        }

        // Decorate event as an ApplicationEvent if necessary
        ApplicationEvent applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        }
        else {
            applicationEvent = new PayloadApplicationEvent<Object>(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) {
            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);
            }
        }
    }

這里為了簡化源碼閱讀的工作量,對一些細節和分支情形做了忽略,只考慮主流程,如上圖箭頭所示,這里調用了事件發布器的multicastEvent()方法進行事件發布,需要傳入事件event和事件類型eventType作為參數。不過通常這個eventType參數為 null,因為事件的類型信息完全可以通過反射的方式從event對象中獲得。繼續跟進源碼:

public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
        //獲取事件類型
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        //遍歷所有和事件匹配的事件監聽器
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                //異步回調監聽方法
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        invokeListener(listener, event);
                    }
                });
            }
            else {
                //同步回調監聽方法
                invokeListener(listener, event);
            }
        }
    }

首先通過傳入的參數或者通過調用resolveDefaultEventType(event)方法獲取事件的事件類型信息,之后會通過getApplicationListeners(event, type)方法得到所有和該事件類型匹配的事件監聽器,其實現邏輯后面會細說,這里先跳過。對于匹配的每一個監聽器,看事件發布器內是否設置了任務執行器實例Executor,決定以何種方式對監聽器的監聽方法進行回調。

  • 若執行器實例Executor未設置,則進行同步回調,即在當前線程執行監聽器的回調方法
  • 若用戶設置了Executor實例(通常而言是線程池),則會進行異步回調,監聽器的監聽方法會交由線程池中的線程去執行。

默認情況下容器不會為用戶創建執行器實例,因而對監聽器的回調是同步進行的,即所有監聽器的監聽方法都在推送事件的線程中被執行,通常這也是處理業務邏輯的線程,若其中一個監聽器回調執行阻塞,則會阻塞整個業務處理的線程,造成嚴重的后果。而異步回調的方式,雖然不會導致業務處理線程被阻塞,但是不能共享一些業務線程的上下文資源,比如類加載器,事務等等。因而究竟選擇哪種回調方式,要視具體業務場景而定。

好了,現在可以來探究下困擾我們很久的一個問題了,那就是:如何根據事件類型找到匹配的所有事件監聽器?這部分邏輯在getApplicationListeners方法中:

protected Collection<ApplicationListener<?>> getApplicationListeners(
            ApplicationEvent event, ResolvableType eventType) {
        //獲取事件中的事件源對象
        Object source = event.getSource();
        //獲取事件源類型
        Class<?> sourceType = (source != null ? source.getClass() : null);
        //以事件類型和事件源類型為參數構建一個cacheKey,用于從緩存map中獲取與之匹配的監聽器列表
        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)))) {
            // 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);
                //將匹配結果緩存到map中
                this.retrieverCache.put(cacheKey, retriever);
                return listeners;
            }
        }
        else {
            // No ListenerRetriever caching -> no synchronization necessary
            return retrieveApplicationListeners(eventType, sourceType, null);
        }
    }

整個流程可以概括為:

  1. 首先從緩存retrieverCache中查找,這個 map 定義在事件發布器的抽象類中AbstractApplicationEventMulticaster:
final Map<ListenerCacheKey, ListenerRetriever> retrieverCache =
            new ConcurrentHashMap<ListenerCacheKey, ListenerRetriever>(64);

ListenerCacheKey由事件類型eventType和事件源類型sourceType構成,ListenerRetriever內部則維護了一個監聽器列表。當所發布的事件類型和事件源類型與 Map 中的 key 匹配時,將直接返回 value 中的監聽器列表作為匹配結果,通常這發生在事件不是第一次發布時,能避免遍歷所有監聽器并進行過濾,如果事件時第一次發布,則會執行流程2。

  1. 遍歷所有的事件監聽器,并根據事件類型和事件源類型進行匹配。這部分邏輯在retrieveApplicationListeners方法中:
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
            ResolvableType eventType, Class<?> sourceType, ListenerRetriever retriever) {
        //這是存放匹配的監聽器的列表
        LinkedList<ApplicationListener<?>> allListeners = new LinkedList<ApplicationListener<?>>();
        Set<ApplicationListener<?>> listeners;
        Set<String> listenerBeans;
        synchronized (this.retrievalMutex) {
            listeners = new LinkedHashSet<ApplicationListener<?>>(this.defaultRetriever.applicationListeners);
            listenerBeans = new LinkedHashSet<String>(this.defaultRetriever.applicationListenerBeans);
        }
        //遍歷所有的監聽器
        for (ApplicationListener<?> listener : listeners) {
            //判斷該事件監聽器是否匹配
            if (supportsEvent(listener, eventType, sourceType)) {
                if (retriever != null) {
                    retriever.applicationListeners.add(listener);
                }
                //將匹配的監聽器加入列表
                allListeners.add(listener);
            }
        }
        //遍歷所有的監聽器 BeanName
        if (!listenerBeans.isEmpty()) {
            BeanFactory beanFactory = getBeanFactory();
            for (String listenerBeanName : listenerBeans) {
                try {
                    Class<?> listenerType = beanFactory.getType(listenerBeanName);
                    if (listenerType == null || supportsEvent(listenerType, eventType)) {
                        // 根據監聽器 BeanName 獲取監聽器實例
                        ApplicationListener<?> listener =
                                beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                        if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
                            if (retriever != null) {
                                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);
        return allListeners;
    }

判斷監聽器是否匹配的邏輯在supportsEvent(listener, eventType, sourceType)中:

protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, Class<?> sourceType) {
        //對原始的ApplicationListener進行一層適配器包裝成為GenericApplicationListener
        GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
                (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
                //判斷監聽器是否支持該事件類型以及該事件源類型
        return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
    }

首先對原始的ApplicationListener進行一層適配器包裝成GenericApplicationListener,便于后面使用該接口中定義的方法判斷監聽器是否支持傳入的事件類型或事件源類型:


public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

   /**
    * Determine whether this listener actually supports the given event type.
    */
   boolean supportsEventType(ResolvableType eventType); //判斷是否支持該事件類型

   /**
    * Determine whether this listener actually supports the given source type.
    */
   boolean supportsSourceType(Class<?> sourceType);   //判斷是否支持該事件源類型

}

smartListener.supportsEventType(eventType)用來判斷監聽器是否支持該事件類型:

public boolean supportsEventType(ResolvableType eventType) {
        if (this.delegate instanceof SmartApplicationListener) {
            Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.resolve();
            return (eventClass != null && ((SmartApplicationListener) this.delegate).supportsEventType(eventClass));
        }
        else {
            return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
        }
    }

因為我們的監聽器實例通常都不是SmartApplicationListener類型,而是GenericApplicationListener,所以直接看 else 語句。
declaredEventType是監聽器泛型的實際類型,而eventType是發布的事件的類型
declaredEventType.isAssignableFrom(eventType)當以下兩種情況返回 true

  1. declaredEventTypeeventType類型相同
  2. declaredEventTypeeventType的父類型

只要監聽器泛型的實際類型和發布的事件類型一樣或是它的父類型,則該監聽器將被成功匹配。

而對于事件源類型的判斷supportsSourceType(Class<?>),由于通常事件監聽器為GenericApplicationListener,故通常會直接返回 true。

public boolean supportsSourceType(Class<?> sourceType) {
        return !(this.delegate instanceof SmartApplicationListener) ||
                ((SmartApplicationListener) this.delegate).supportsSourceType(sourceType);
    }

最后,將所有匹配的監聽器與其事件類型緩存到retrieverCache這個 map 中。

Collection<ApplicationListener<?>> listeners =
                        retrieveApplicationListeners(eventType, sourceType, retriever);
                //將匹配結果緩存到map中
                this.retrieverCache.put(cacheKey, retriever);
                return listeners;

梳理下容器事件發布的整個流程,可以總結如下:


5. 總結

這篇文章主要是為了梳理下 Spring 的容器內事件體系并對其工作原理做一定程度上的源碼上的剖析。Spring Boot 的事件監聽機制復用了 Spring 的內建事件體系。但是 Spring 內建事件為 Spring 應用上下文事件,即ApplicationContextEvent,其事件源為ApplicationContext。而 Spring Boot 的事件類型是SpringApplicationEvent,事件源則是SpringApplication。Spring Boot 事件監聽手段僅為SpringApplication關聯的ApplicationListener集合。其關聯途徑有二:

  1. SpringApplication構造階段在 Class Path 下所加載的 META-INF/spring.factories 資源中的ApplicationListener對象集合。
  2. 通過方法SpringApplication#addListeners(ApplicationListener...)SpringApplicationBuilder#listeners(ApplicationListener...)顯式裝配。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容