1. 程式人生 > >深入理解Spring的容器內事件發布監聽機制

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

not main alt 事件類型 http inner interface 改變 pear

目錄

  • 1. 什麽是事件監聽機制
  • 2. JDK中對事件監聽機制的支持
    • 2.1 基於JDK實現對任務執行結果的監聽
  • 3.Spring容器對事件監聽機制的支持
    • 3.1 基於Spring實現對任務執行結果的監聽
  • 4.Spring事件監聽源碼解析
    • 4.1 初始化事件發布器流程
    • 4.2 註冊事件監聽器流程
    • 4.3 容器事件發布流程
  • 5.總結

1. 什麽是事件監聽機制

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

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

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

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

技術分享圖片

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

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

Spring框架對事件的發布與監聽提供了相對完整的支持,它擴展了JDK中對自定義事件監聽提供的基礎框架,並與Spring的IOC特性作了整合,使得用戶可以根據自己的業務特點進行相關的自定義,並依托Spring容器方便的實現監聽器的註冊和事件的發布。因為Spring的事件監聽依托於JDK提供的底層支持,為了更好的理解,先來看下JDK中為用戶實現自定義事件監聽提供的基礎框架。

2. JDK中對事件監聽機制的支持

JDK為用戶實現自定義事件監聽提供了兩個基礎的類。一個是代表所有可被監聽事件的事件基類java.util.EventObject,所有自定義事件類型都必須繼承該類,類結構如下所示

public class EventObject implements java.io.Serializable {

    private static final long serialVersionUID = 5516075349620653480L;

    /**
     * The object on which the Event initially occurred.
     */
    protected transient Object  source;

    /**
     * Constructs a prototypical Event.
     *
     * @param    source    The object on which the Event initially occurred.
     * @exception  IllegalArgumentException  if source is null.
     */
    public EventObject(Object source) {
        if (source == null)
            throw new IllegalArgumentException("null source");

        this.source = source;
    }

    /**
     * The object on which the Event initially occurred.
     *
     * @return   The object on which the Event initially occurred.
     */
    public Object getSource() {
        return source;
    }

    /**
     * Returns a String representation of this EventObject.
     *
     * @return  A a String representation of this EventObject.
     */
    public String toString() {
        return getClass().getName() + "[source=" + source + "]";
    }
}

該類內部有一個Object類型的source變量,邏輯上表示發生該事件的事件源,實際中可以用來存儲包含該事件的一些相關信息。
另一個則是對所有事件監聽器進行抽象的接口java.util.EventListener,這是一個標記接口,內部沒有任何抽象方法,所有自定義事件監聽器都必須實現該標記接口

/**
 * A tagging interface that all event listener interfaces must extend.
 * @since JDK1.1
 */
public interface EventListener {
}

以上就是JDK為我們實現自定義事件監聽提供的底層支持。針對具體業務場景,我們通過擴展java.util.EventObject來自定義事件類型,同時通過擴展java.util.EventListener來定義在特定事件發生時被觸發的事件監聽器。當然,不要忘了還要定義一個事件發布器來管理事件監聽器並提供發布事件的功能。

2.1 基於JDK實現對任務執行結果的監聽

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

  • 任務結束事件的事件源
    因為要對任務執行結束這一事件進行監聽,所以必須對任務這一概念進行定義,如下
/**
 * @author: takumiCX
 * @create: 2018-11-02
 **/
@Data
public class Task {

    private String name;

    private TaskFinishStatus status;

}

任務包含任務名和任務狀態,其中任務狀態是個枚舉常量,只有成功和失敗兩種取值。

/**
 * @author: takumiCX
 * @create: 2018-11-02
 **/
public enum  TaskFinishStatus {
    SUCCEDD,
    FAIL;
}
  • 任務結束事件TaskFinishEvent
    自定義事件類型TaskFinishEvent繼承自JDK中的EventObject,構造時會傳入Task作為事件源。
/**
 * @author: takumiCX
 * @create: 2018-11-02
 **/
public class TaskFinishEvent extends EventObject {
    /**
     * Constructs a prototypical Event.
     *
     * @param source The object on which the Event initially occurred.
     * @throws IllegalArgumentException if source is null.
     */
    public TaskFinishEvent(Object source) {
        super(source);
    }
}
  • 該事件的監聽器抽象
    繼承標記接口EventListner表示該接口的實現類是一個監聽器,同時在內部定義了事件發生時的響應方法onTaskFinish(event),接收一個TaskFinishEvent作為參數。
/**
 * @author: takumiCX
 * @create: 2018-11-02
 **/
public interface TaskFinishEventListner extends EventListener {
    
    void onTaskFinish(TaskFinishEvent event);
}
  • 郵件服務監聽器
    該郵件服務監聽器將在監聽到任務結束事件時將任務的執行結果發送給用戶。
/**
 * @author: takumiCX
 * @create: 2018-11-03
 **/
@Data
public class MailTaskFinishListener implements TaskFinishEventListner {

    private String emial;

    @Override
    public void onTaskFinish(TaskFinishEvent event) {
        System.out.println("Send Emial to "+emial+" Task:"+event.getSource());
    }
}
  • 自定義事件發布器
/**
 * @author: takumiCX
 * @create: 2018-11-03
 **/
public class TaskFinishEventPublisher {
    
    private List<TaskFinishEventListner> listners=new ArrayList<>();
    
    //註冊監聽器
    public synchronized void register(TaskFinishEventListner listner){
        if(!listners.contains(listner)){
            listners.add(listner);
        }
    }
    
    //移除監聽器
    public synchronized boolean remove(TaskFinishEventListner listner){
        return listners.remove(listner);
    }
    
    
    //發布任務結束事件
    public void publishEvent(TaskFinishEvent event){
        
        for(TaskFinishEventListner listner:listners){
            listner.onTaskFinish(event);
        }
    }
}
  • 測試代碼如下
/**
 * @author: takumiCX
 * @create: 2018-11-03
 **/
public class TestTaskFinishListener {


    public static void main(String[] args) {

        //事件源
        Task source = new Task("用戶統計", TaskFinishStatus.SUCCEDD);

        //任務結束事件
        TaskFinishEvent event = new TaskFinishEvent(source);

        //郵件服務監聽器
        MailTaskFinishListener mailListener = new MailTaskFinishListener("[email protected]");

        //事件發布器
        TaskFinishEventPublisher publisher = new TaskFinishEventPublisher();

        //註冊郵件服務監聽器
        publisher.register(mailListener);

        //發布事件
        publisher.publishEvent(event);

    }
}
  • 測試結果
    技術分享圖片

如果後期因為需求變動需要在任務結束時將結果以短信的方式發送給用戶,則可以再添加一個短信服務監聽器

/**
 * @author: takumiCX
 * @create: 2018-11-03
 **/
@Data
@AllArgsConstructor
public class SmsTaskFinishListener implements TaskFinishEventListner {

    private String address;

    @Override
    public void onTaskFinish(TaskFinishEvent event) {
        System.out.println("Send Message to "+address+" Task:"+event.getSource());
    }
}

在測試代碼中添加如下代碼向事件發布器註冊該監聽器

SmsTaskFinishListener smsListener = new SmsTaskFinishListener("123456789");

//註冊短信服務監聽器
publisher.register(smsListener);

最後運行結果如下
技術分享圖片

基於JDK的支持要實現對自定義事件的監聽還是比較麻煩的,要做的工作比較多。而且自定義的事件發布器也不能提供對所有事件的統一發布支持。基於Spring框架實現自定義事件監聽則要簡單很多,功能也更加強大。

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

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

  • 事件
    Spring為容器內事件定義了一個抽象類ApplicationEvent,該類繼承了JDK中的事件基類EventObject。因而自定義容器內事件除了需要繼承ApplicationEvent之外,還要傳入事件源作為構造參數。
    技術分享圖片

  • 事件監聽器
    Spring定義了一個ApplicationListener接口作為為事件監聽器的抽象,接口定義如下
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抽象類內部,如下所示

/** Helper class used in event publishing */
private ApplicationEventMulticaster applicationEventMulticaster;

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

3.1 基於Spring實現對任務執行結果的監聽

業務場景在2.1中已經介紹過了,這裏就不在啰嗦。基於Spring框架來實現對自定義事件的監聽流程十分簡單,只需要三部:1.自定義事件類 2.自定義事件監聽器並向容器註冊 3.發布事件

  • 1.自定任務結束事件
    定義一個任務結束事件TaskFinishEvent2,該類繼承抽象類ApplicationEvent來遵循容器事件規範。
/**
 * @author: takumiCX
 * @create: 2018-11-04
 **/
public class TaskFinishEvent2 extends ApplicationEvent {
    /**
     * Create a new ApplicationEvent.
     *
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public TaskFinishEvent2(Object source) {
        super(source);
    }
}
  • 2.自定義郵件服務監聽器並向容器註冊
    該類實現了容器事件規範定義的監聽器接口,通過泛型參數指定對上面定義的任務結束事件進行監聽,通過@Component註解向容器進行註冊
/**
 * @author: takumiCX
 * @create: 2018-11-04
 **/
@Component
public class MailTaskFinishListener2 implements ApplicationListener<TaskFinishEvent2> {

    private String emial="[email protected]";
    
    @Override
    public void onApplicationEvent(TaskFinishEvent2 event) {
        
        System.out.println("Send Emial to "+emial+" Task:"+event.getSource());
        
    }
}
  • 3.發布事件
    從上面對Spring事件監聽機制的類結構分析可知,發布事件的功能定義在ApplicationEventPublisher接口中,而ApplicationContext繼承了該接口,所以最好的方法是通過實現ApplicationContextAware接口獲取ApplicationContext實例,然後調用其發布事件方法。如下所示定義了一個發布容器事件的代理類
/**
 * @author: takumiCX
 * @create: 2018-11-04
 **/
@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;
    }
}

在此基礎上,還可以自定義一個短信服務監聽器,在任務執行結束時發送短信通知用戶。過程和上面自定義郵件服務監聽器類似:實現ApplicationListner接口並重寫抽象方法,然後通過註解或者xml的方式向容器註冊。

4.Spring事件監聽源碼解析

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

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

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

4.1 初始化事件發布器流程

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

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 + "]");
   }
  }
 }

這裏會根據核心容器beanFactory中是否有id為applicationEventMulticaster的bean分兩種情況:

  • 1.容器中已有id為applicationEventMulticaster的bean
    直接從容器緩存獲取或是創建該bean實例,並交由成員變量applicationEventMulticaster保存。
    當用戶自定義了事件發布器並向容器註冊時會執行該流程。

  • 2.容器中不存在applicationEventMulticaster的bean
    這是容器默認的執行流程,會創建一個SimpleApplicationEventMulticaster,其僅在實現事件發布器基本功能(管理事件監聽器以及發布容器事件)的前提下,增加了可以設置任務執行器
    Executor和錯誤處理器ErrorHandler的功能,當設置Executor為線程池時,則會以異步的方式對事件監聽器進行回調,而ErrorHandler允許我們在回調方法執行錯誤時進行自定義處理。默認情況下,
    這兩個變量都為null。
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

   private Executor taskExecutor;

   private ErrorHandler errorHandler;
public abstract class AbstractApplicationEventMulticaster
      implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {

   private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);

   final Map<ListenerCacheKey, ListenerRetriever> retrieverCache =
         new ConcurrentHashMap<ListenerCacheKey, ListenerRetriever>(64);

   private ClassLoader beanClassLoader;

   private BeanFactory beanFactory;

   private Object retrievalMutex = this.defaultRetriever;

之後會調用beanFactory.registerSingleton方法將創建的SimpleApplicationEventMulticaster實例註冊為容器的單實例bean。

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

4.2 註冊事件監聽器流程

註冊事件監聽器的流程在初始化事件發布器之後,如下圖所示
技術分享圖片

其關鍵代碼如下所示

// uninitialized to let post-processors apply to them!
        //獲取實現ApplicationListener接口的所有bean的beanName
        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
        for (String listenerBeanName : listenerBeanNames) {
            //將監聽器的beanName保存到事件發布器中
            getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
        }

首先遍歷beanFactory中所有的bean,獲取所有實現ApplicationListener接口的bean的beanName,並將這些beanName註冊到ApplicationEventMulticaster中

@Override
public void addApplicationListenerBean(String listenerBeanName) {
   synchronized (this.retrievalMutex) {
      //保存所有監聽器的beanName
      this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
      //清除維護事件和監聽器映射關系的緩存
      this.retrieverCache.clear();
   }
}

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

private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);

其以內部類形式定義在AbstractApplicationEventMulticaster中

    /**
     * Helper class that encapsulates a specific set of target listeners,
     * allowing for efficient retrieval of pre-filtered listeners.
     * <p>An instance of this helper gets cached per event type and source type.
     */
    private class ListenerRetriever {

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

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

向事件發布器註冊監聽器的beanName,其實就是將beanName加入ListenerRetriever的集合中。

其實看到這裏會有一個疑問,registerListeners()方法只是找到了所有監聽器的beanName並將其保存到了事件發布器ApplicationEventMulticaster中,那麽真正的事件監聽器實例是何時被創建並被加入到事件發布器中的?
這裏我們不得不退回到啟動容器的refresh()方法中,在初始化beanFactory之後,初始化事件發布器之前,容器在prepareBeanFactory(beanFactory)方法中又註冊了一些重要組件,其中就包括一個特殊的BeanPostProcessor:ApplicationListenerDetector,正如其類名暗示的那樣,這是一個事件監聽器的探測器。
技術分享圖片

技術分享圖片

該類實現了BeanPostProcessor接口,如下圖所示
技術分享圖片

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

@Override
    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後,該ApplicationListenerDetector的postProcessAfterInitialization(Object bean, String beanName)方法會被作用在每一個bean上,通過判斷傳入的bean
是否是ApplicationListener實例進行過濾,然後將找到的事件監聽器bean註冊到事件發布器中。

4.3 容器事件發布流程

技術分享圖片

技術分享圖片

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

@Override
    public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
        //獲取事件類型
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        //遍歷所有和事件匹配的事件監聽器
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            //獲取事件發布器內的任務執行器,默認該方法返回null
            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.首先從緩存map中查找,這個map定義在事件發布器的抽象類中
final Map<ListenerCacheKey, ListenerRetriever> retrieverCache =
      new ConcurrentHashMap<ListenerCacheKey, ListenerRetriever>(64);

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

  • 2.遍歷所有的事件監聽器,並根據事件類型和事件源類型進行匹配。
Collection<ApplicationListener<?>> listeners =
      retrieveApplicationListeners(eventType, sourceType, retriever);
/**
     * 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, 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);
            }
        }
        //這部分可以跳過
        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) {
                                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)用來判斷監聽器是否支持該事件類型,因為我們的監聽器實例通常都不是SmartApplicationListener類型,所以直接看下面箭頭所指的方法就好
技術分享圖片

declaredEventType是監聽器泛型的實際類型,而eventType是發布的事件的類型
declaredEventType.isAssignableFrom(eventType)當以下兩種情況返回true

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

而對於事件源類型而言,通常默認會直接返回true,也就是說事件源的類型通常對於判斷匹配的監聽器沒有意義。
技術分享圖片

這裏查找到所有匹配的監聽器返回後會將匹配關系緩存到retrieverCache這個map中

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

梳理下容器事件發布的整個流程,可以總結如下
技術分享圖片

5.總結

這篇文章主要是為了梳理下Spring的容器內事件體系並對其工作原理做一定程度上的源碼上的剖析,本來還想展示一些關於Spring事件發布監聽機制的一些擴展特性和額外功能,不過由於前面廢話太多導致篇幅
已經大大超出了預期,後面這部分只能有時間另開一篇再說了。

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