Spring筆記(7) - Spring的事件和監聽機制

碼猿手發表於2020-11-12

一.背景

  事件機制作為一種程式設計機制,在很多開發語言中都提供了支援,同時許多開源框架的設計中都使用了事件機制,比如SpringFramework。

  在 Java 語言中,Java 的事件機制參與者有3種角色:

    1.Event Source:具體的事件源,比如說,你在介面點選一個 button 按鈕,那麼這個按鈕就是事件源,要想使按鈕對某些事件進行響應,你就需要註冊特定的監聽器 listener,事件源將事件物件傳遞給所有註冊的監聽器;

    2.Event Object:事件狀態物件,用於監聽器的相應的方法之中,作為引數;

    3.Event Listener:事件監聽器,當它監聽到 event object 產生的時候,它就呼叫相應的方法進行處理;

    上面3個角色就定義了事件機制的基本模型。

    流程是:

      1)首先我們將監聽器物件註冊給事件源物件,這樣當事件觸發時系統便可以通過事件源訪問相應的監聽器;

      2)當事件源觸發事件後,系統將事件的相關資訊封裝成相應型別的事件物件,並將其傳送給註冊到事件源的相應監聽器;

      3)當事件物件傳送給監聽器後,系統呼叫監聽器相應的事件處理方法對事件進行處理,也就是做出響應;

  PS:監聽器與事件源之間是“多對多”的關係。

  下面對幾個概念進行詳細介紹:

   1)事件源:事件最初由事件源產生,事件源可以是 Java Bean 或由生成事件能力的物件。在 Java 中,每一個元件會產生什麼樣的事件,已經被定義好了。或者說,對於任何一個事件來說,哪些元件可以產生它,已經是確定的了。

   2)事件物件:在事件物件中最高層是 java.util.EventObject,所有事件狀態物件都是繼承該類的派生類,比如 Spring-context 的事件機制的根類為 ApplicationEvent,而 Apollo 配置中心的事件處理類 AppCreationEvent 是繼承自 Spring 的;

    除了 EventObject是在 util 包中,其它都在 java.awt、java.awt.event 包或 java.swing、java.swing.event 包中,比如有 AWTEvent、ActionEvent、AdjustmentEvent等等;

    • EventObject:該類除了從 Object 類中繼承下來的方法外還有一個 getSource 方法,其功能就是返回最初發生 Event 的物件;
      public class EventObject implements java.io.Serializable {
      
          protected transient Object  source;
      
          public EventObject(Object source) {
              if (source == null)
                  throw new IllegalArgumentException("null source");
      
              this.source = source;
          }
      
          public Object getSource() {
              return source;
          }
      
          public String toString() {
              return getClass().getName() + "[source=" + source + "]";
          }
      }
    • ApplicationEvent:
      public abstract class ApplicationEvent extends EventObject {
          private static final long serialVersionUID = 7099057708183571937L;
          private final long timestamp = System.currentTimeMillis();
      
          public ApplicationEvent(Object source) {
              super(source);
          }
      
          public final long getTimestamp() {
              return this.timestamp;
          }
      }
    • AppCreationEvent:
      public class AppCreationEvent extends ApplicationEvent {
      
        public AppCreationEvent(Object source) {
          super(source);
        }
      
        public App getApp() {
          Preconditions.checkState(source != null);
          return (App) this.source;
        }
      }

   3) 監聽器物件:監聽器物件就是一個實現了特定監聽器介面的類的例項,在監聽器介面的最頂層介面是 java.util.EventListener,這個介面是所有事件偵聽器介面必須擴充套件的標記介面。感到詫異的是這個介面完全是空的,裡面沒有任何的抽象方法的定義;

public interface EventListener {
}

  事件監聽器的介面命名方式為:XXListener,而且,在java中,這些介面已經被定義好了。用來被實現,它定義了事件處理器(即事件處理的方法原型,這個方法需要被重新實現)。例如:ActionListener介面、MouseListener介面、WindowListener介面等;

  說了這麼多,現在要回到正題來,說到 Spring 的事件機制,就得從 Spring 的容器開始說起,在 IOC 容器的啟動過程,當所有的 bean 都已經初始化處理完成之後,Spring IOC 容器會有一個釋出事件的動作。

public void refresh() throws BeansException, IllegalStateException {
        //來個鎖,不然 refresh() 還沒結束,你又來個啟動或銷燬容器的操作,那不就亂套了嘛
        synchronized (this.startupShutdownMonitor) {
                
                ..............
                //初始化容器的資訊源
                initMessageSource();

                //初始化事件監聽多路廣播器
                initApplicationEventMulticaster();

                //是個空殼方法,在AnnotationApplicationContex上下文中沒有實現,可能在spring後面的版本會去擴充套件。
                onRefresh();

                //註冊監聽器
                registerListeners();

                
                //物件的建立:初始化剩下所有的(非懶載入的)單例項物件
                finishBeanFactoryInitialization(beanFactory);

                //重新整理完成工作,包括初始化LifecycleProcessor,釋出重新整理完成事件等
                finishRefresh();
        }
            .................
}

   在 AbstractApplicationContext 類的 finishRefresh 方法,裡面就會發布(廣播)一條代表初始化結束的訊息:

    protected void finishRefresh() {
        // Clear context-level resource caches (such as ASM metadata from scanning).
        clearResourceCaches();

        // Initialize lifecycle processor for this context.
        initLifecycleProcessor();

        // Propagate refresh to lifecycle processor first.
        getLifecycleProcessor().onRefresh();

        //釋出(廣播)一條訊息,型別ContextRefreshedEvent代表Spring容器初始化結束
        publishEvent(new ContextRefreshedEvent(this));

        // Participate in LiveBeansView MBean, if active.
        LiveBeansView.registerApplicationContext(this);
    }

   這樣,當 Spring IOC 容器載入處理完相應的 bean 之後,也給我們提供了一個機會,可以去做一些自己想做的事情。這也是 Spring IOC 容器提供給外部擴充套件的地方,我們可以使用這個擴充套件機制,來實現一些特殊的業務需求。

  比如讓我們的 bean 實現  ApplicationListener 介面,這樣當釋出事件時, Spring 的 IOC 容器就會以容器中的例項物件作為事件源類,並從中找到事件的監聽者,此時會執行相應的方法比如 onApplicationEvent(E event),我們的業務邏輯程式碼就會寫在這個方法裡面。這樣我們的目的就可以達到了,但你這是可能會有一個疑問,這樣的程式碼我們可以通過實現 InitializingBean 介面來實現啊,也會被 Spring 容器自動呼叫。但如果我們這時的需求是我們要做的事情,是必要要等到所有的 bean 都被處理完成之後再進行,此時 InitializingBean 介面就步合適了。(可檢視下文案例3)

   Spring 的事件機制是觀察者設計模式的實現,通過 ApplicationEvent 和 ApplicationListener 介面,可以實現容器事件處理。

  ApplicationListener 監聽 ApplicationEvent 及其子類的事件:

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    void onApplicationEvent(E event);

}

  如果容器中有一個監聽器,每當容器釋出事件時,監聽器將自動被觸發,當然這種事件機制必須需要程式顯示的觸發。

  其中 Spring 有一些內建的事件,當完成某種操作時會發出某些事件動作。比如上文中的監聽 ContextRefreshedEvent 事件,當所有的bean都初始化完成併成功裝載後會觸發該事件,實現 ApplicationListener<ContextRefreshedEvent> 介面可以收到監聽動作,如何實現自己的業務邏輯。

  下面是一些 Spring 的內建事件:

    • ContextRefreshedEvent:ApplicationContext 被初始化或重新整理時,該事件被髮布。這也可以在 ConfigurableApplicationContext 介面中使用 refresh() 方法來發生。此處的初始化是指:所有的Bean 被成功裝載,後置處理器 Bean 被檢測並啟用,所有 Singleton Bean 被預例項化,ApplicationContext 容器已就緒可用;【容器重新整理完成所有 bean 都完全建立會發布這個事件】
    • ContextStartedEvent:當使用 ConfigurableApplicationContext (ApplicationContext子介面)介面中的 start() 方法啟動 ApplicationContext 時,該事件被髮布。你可以調查你的資料庫,或者你可以在接受到這個事件後重啟任何停止的應用程式;
    • ContextStoppedEvent:當使用 ConfigurableApplicationContext 介面中的 stop() 停止 ApplicationContext 時,釋出這個事件。你可以在接受到這個事件後做必要的清理的工作;
    • ContextClosedEvent:當使用 ConfigurableApplicationContext 介面中的 close() 方法關閉 ApplicationContext 時,該事件被髮布。一個已關閉的上下文到達生命週期末端,它不能被重新整理或重啟;【關閉容器會發布這個事件】

    • RequestHandledEvent:這是一個 web-specific 事件,告訴所有 bean HTTP 請求已經被服務。只能應用於使用 DispatcherServlet 的 Web 應用。在使用 Spring 作為前端的MVC控制器時,當Spring處理使用者請求結束後,系統會自動觸發該事件;

 二.案例

   1.簡單實現自定義監聽器,初步認識監聽器:結合上篇文章案例,實現監聽器監聽 Spring 容器變化

@Configuration
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
    //當容器釋出此事件之後,方法觸發
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("當前收到的事件:"+event);
    }
}
=========測試執行結果=========
[MyBeanDefinitionRegistryPostProcessor]postProcessBeanDefinitionRegistry--->bean的數量:11
十一月 03, 2020 10:17:59 下午 org.springframework.context.annotation.ConfigurationClassPostProcessor enhanceConfigurationClasses
資訊: Cannot enhance @Configuration bean definition 'myBeanDefinitionRegistryPostProcessor' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'.
[MyBeanDefinitionRegistryPostProcessor]postProcessBeanFactory--->bean的數量:12
[MyBeanFactoryPostProcessor]呼叫了postProcessBeanFactory
[MyBeanFactoryPostProcessor]當前beanFactory共有12個bean
[MyBeanFactoryPostProcessor]當前beanFactory有下面元件[org.springframework.context.annotation.internalConfigurationAnnotationProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor, org.springframework.context.event.internalEventListenerProcessor, org.springframework.context.event.internalEventListenerFactory, extConfig, myApplicationListener, myBeanDefinitionRegistryPostProcessor, myBeanFactoryPostProcessor, myBeanPostProcessor, person, color]
PropertyValues: length=0
[MyBeanFactoryPostProcessor]postProcessBeanFactory方法中修改了name屬性初始值了
PropertyValues: length=1; bean property 'name'
[MyBeanPostProcessor]後置處理器處理bean=【extConfig】開始
[MyBeanPostProcessor]後置處理器處理bean=【extConfig】完畢!
[MyBeanPostProcessor]後置處理器處理bean=【myApplicationListener】開始
[MyBeanPostProcessor]後置處理器處理bean=【myApplicationListener】完畢!
Person有參構造器:[name=張三,sex=男]
[Person]呼叫了BeanNameAware的setBeanName方法了:person
[Person]呼叫了BeanFactoryAware的setBeanFactory方法了:org.springframework.beans.factory.support.DefaultListableBeanFactory@e45f292: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,extConfig,myApplicationListener,myBeanDefinitionRegistryPostProcessor,myBeanFactoryPostProcessor,myBeanPostProcessor,person,color]; root of factory hierarchy
[MyBeanPostProcessor]後置處理器處理bean=【person】開始
[Person]呼叫了Initailization的afterPropertiesSet方法了
[MyBeanPostProcessor]後置處理器處理bean=【person】完畢!
[MyBeanPostProcessor]後置處理器處理bean=【color】開始
[MyBeanPostProcessor]後置處理器處理bean=【color】完畢!
當前收到的事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Tue Nov 03 22:17:59 CST 2020]
Person [name=趙四, sex=null]
當前收到的事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Tue Nov 03 22:17:59 CST 2020]
[Person]呼叫了DisposableBean的destroy方法了

   2.自定義釋出一個事件,步驟如下:

    1)實現一個監聽器來監聽某個事件(事件是實現了ApplicationEvent 及其子類的事件);

    2)把監聽器加入到容器中;

    2)釋出事件,只要容器有相關事件的釋出,我們就能監聽到這個事件;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ExtConfig.class);
        Person bean = context.getBean(Person.class);
        System.out.println(bean.toString());
        //釋出事件
        context.publishEvent(new ApplicationEvent(new String("自定義事件")) {
            });
        context.close();
    }
=========測試執行結果=========
[MyBeanDefinitionRegistryPostProcessor]postProcessBeanDefinitionRegistry--->bean的數量:11
...............
[MyBeanPostProcessor]後置處理器處理bean=【color】完畢!
當前收到的事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Tue Nov 03 22:40:39 CST 2020]
Person [name=趙四, sex=null]
當前收到的事件:com.hrh.ext.ExtTest$1[source=自定義事件]
當前收到的事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Tue Nov 03 22:40:39 CST 2020]
[Person]呼叫了DisposableBean的destroy方法了

    從上面的執行結果可以看出,我們在容器中新增了一個事件,當我們釋出事件時,監聽器會監聽到事件並把事件的內容釋出出來。

    3.自定義監聽器,實現容器的 bean都初始化後執行相應的操作,比如執行特定的方法:

    1)自定義註解:

//該註解作用在類上
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BaseService {
}

    2)兩個測試 Mapper:

@Configuration
@BaseService
public class TaskScheduleJobMapper {
    public void initMapper() {
        System.out.println(">>>>> 【initMapper】Start job <<<<<");
    }
}


@Configuration
@BaseService
public class TaskScheduleJobTxlogMapper {
    public void initMapper() {
        System.out.println(">>>>> 【initMapper】Recording log <<<<<");
    }
}

    3)測試系統入口:

public interface BaseInterface {
    public void init();
}

@Configuration
public class BaseInterfaceImpl implements BaseInterface {
    @Override
    public void init() {
        System.out.println("System start........");
    }
}    

    4)自定義監聽器:

@Configuration
public class ApplicationContextListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // root application context
        if (null == contextRefreshedEvent.getApplicationContext().getParent()) {
            System.out.println(">>>>> Spring初始化完畢 <<<<<");
            // spring初始化完畢後,通過反射呼叫所有使用BaseService註解的initMapper方法
            Map<String, Object> baseServices =
                    contextRefreshedEvent.getApplicationContext().getBeansWithAnnotation(BaseService.class);
            for (Object service : baseServices.values()) {
                System.out.println(">>>>> {" + service.getClass().getName() + "}.initMapper():");
                try {
                    Method initMapper = service.getClass().getMethod("initMapper");
                    initMapper.invoke(service);
                } catch (Exception e) {
                    System.out.println("初始化BaseService的initMapper方法異常:" + e);
                    e.printStackTrace();
                }
            }
            // 系統入口初始化
            Map<String, BaseInterface> baseInterfaceBeans =
                    contextRefreshedEvent.getApplicationContext().getBeansOfType(BaseInterface.class);
            for (Object service : baseInterfaceBeans.values()) {
                System.out.println(">>>>> {" + service.getClass().getName() + "}.init()");
                try {
                    Method init = service.getClass().getMethod("init");
                    init.invoke(service);
                } catch (Exception e) {
                    System.out.println("初始化BaseInterface的init方法異常:" + e);
                    e.printStackTrace();
                }
            }
        }
    }
}


=======測試執行結果=======
[MyBeanDefinitionRegistryPostProcessor]postProcessBeanDefinitionRegistry--->bean的數量:14
................
[MyBeanPostProcessor]後置處理器處理bean=【color】開始
[MyBeanPostProcessor]後置處理器處理bean=【color】完畢!
>>>>> Spring初始化完畢 <<<<<
>>>>> {com.hrh.ext.TaskScheduleJobMapper$$EnhancerBySpringCGLIB$$6bfe7114}.initMapper():
>>>>> 【initMapper】Start job <<<<<
>>>>> {com.hrh.ext.TaskScheduleJobTxlogMapper$$EnhancerBySpringCGLIB$$7132ffe6}.initMapper():
>>>>> 【initMapper】Recording log <<<<<
>>>>> {com.hrh.ext.BaseInterfaceImpl$$EnhancerBySpringCGLIB$$f49a26ba}.init()
System start........
Person [name=趙四, sex=null]
[Person]呼叫了DisposableBean的destroy方法了

  4.自定義事件及監聽並進行釋出

    1)自定義事件:

public class EmailEvent extends ApplicationEvent {
    private String address;
    private String text;

    /**
     * Create a new {@code ApplicationEvent}.
     *
     * @param source the object on which the event initially occurred or with
     *               which the event is associated (never {@code null})
     */
    public EmailEvent(Object source) {
        super(source);
    }

    public EmailEvent(Object source, String address, String text) {
        super(source);
        this.address = address;
        this.text = text;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

    2)自定義監聽器監聽自定義事件:

@Component
public class EmailListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof EmailEvent) {

            EmailEvent emailEvent = (EmailEvent) event;
            System.out.println("郵件地址:" + emailEvent.getAddress());
            System.out.println("郵件內容:" + emailEvent.getText());
        } else {
            System.out.println("容器本身事件:" + event);
        }

    }
}

     3)測試釋出事件:

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ExtConfig.class);

        Person bean = context.getBean(Person.class);
        System.out.println(bean.toString());
        //建立一個ApplicationEvent物件
        EmailEvent event = new EmailEvent("hello","249968839@qq.com","This is a event test");
        //主動觸發該事件
        context.publishEvent(event);
        context.close();
    }
=======測試執行結果=======
[MyBeanDefinitionRegistryPostProcessor]postProcessBeanDefinitionRegistry--->bean的數量:15
.............
>>>>> Spring初始化完畢 <<<<<
>>>>> {com.hrh.ext.TaskScheduleJobMapper$$EnhancerBySpringCGLIB$$45955a2d}.initMapper():
>>>>> 【initMapper】Start job <<<<<
>>>>> {com.hrh.ext.TaskScheduleJobTxlogMapper$$EnhancerBySpringCGLIB$$4ac9e8ff}.initMapper():
>>>>> 【initMapper】Recording log <<<<<
>>>>> {com.hrh.ext.BaseInterfaceImpl$$EnhancerBySpringCGLIB$$e5b13f13}.init()
System start........
容器本身事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e5d6d97, started on Fri Nov 06 22:55:23 CST 2020]
Person [name=趙四, sex=null]
容器本身事件:com.hrh.ext.ExtTest$1[source=自定義事件]
郵件地址:249968839@qq.com
郵件內容:This is a event test
容器本身事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e5d6d97, started on Fri Nov 06 22:55:23 CST 2020]
[Person]呼叫了DisposableBean的destroy方法了

三.原理

  上面說了幾個案例,現在你應該知道怎麼使用了吧,接下來通過 debug 程式碼來分析它的執行流程。

  1.先來看看 ContextRefreshedEvent事件是怎麼釋出的,還是從熟悉的配方 refresh() 講起,當容器重新整理後,可以看到它呼叫了 finishRefresh() 來重新整理執行事件:

public void refresh() throws BeansException, IllegalStateException {
        //來個鎖,不然 refresh() 還沒結束,你又來個啟動或銷燬容器的操作,那不就亂套了嘛
        synchronized (this.startupShutdownMonitor) {
                
                ..............
                //初始化容器的資訊源
                initMessageSource();

                //初始化事件監聽多路廣播器
                initApplicationEventMulticaster();

                //是個空殼方法,在AnnotationApplicationContex上下文中沒有實現,可能在spring後面的版本會去擴充套件。
                onRefresh();

                //註冊監聽器
                registerListeners();

                
                //物件的建立:初始化剩下所有的(非懶載入的)單例項物件
                finishBeanFactoryInitialization(beanFactory);

                //重新整理完成工作,包括初始化LifecycleProcessor,釋出重新整理完成事件等
                finishRefresh();
        }
            .................
}

  2.在 AbstractApplicationContext.finishRefresh 方法中就會發布(廣播)一條代表初始化結束的訊息:publishEvent(new ContextRefreshedEvent(this))

    protected void finishRefresh() {
        // Clear context-level resource caches (such as ASM metadata from scanning).
        clearResourceCaches();

        // Initialize lifecycle processor for this context.
        initLifecycleProcessor();

        // Propagate refresh to lifecycle processor first.
        getLifecycleProcessor().onRefresh();

        //釋出(廣播)一條訊息,型別ContextRefreshedEvent代表Spring容器初始化結束
        publishEvent(new ContextRefreshedEvent(this));

        // Participate in LiveBeansView MBean, if active.
        LiveBeansView.registerApplicationContext(this);
    }

  3.繼續 publishEvent 方法會發現執行了getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType) 來廣播訊息:

    public void publishEvent(ApplicationEvent event) {
        publishEvent(event, null);
    }
    
    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) event;//型別轉換
        }
        else {
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
            }
        }

        // Multicast right now if possible - or lazily once the multicaster is initialized
        //初始化過程中的registerListeners方法會把earlyApplicationEvents設定為空,(早期事件,容器初始化時使用,可以忽略)
        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);
            }
        }
    }
    

    PS:publishEvent 方法是釋出(廣播)服務的核心能力,而它定義在 ApplicationEventPublisher 介面中,ApplicationContext介面繼承了 ApplicationEventPublisher,所以 AbstractApplicationContext抽象類(ApplicationContext介面的實現類)就實現了該方法,也具有了傳送廣播的能力。

    上面的 getApplicationEventMulticaster() 是什麼東西呢?需要深入瞭解下,它的作用是獲取事件的多播器(派發器),即將事件傳送給多個監聽器,讓監聽器執行相應的邏輯。

    private ApplicationEventMulticaster applicationEventMulticaster;
    ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
        if (this.applicationEventMulticaster == null) {
            throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
                    "call 'refresh' before multicasting events via the context: " + this);
        }
        return this.applicationEventMulticaster;
    }

    從上面的程式碼可以看出,applicationEventMulticaster是 AbstractApplicationContext 的私有成員變數,那麼這個多播器(派發器)是怎麼獲取到的呢?在前面的 refresh 方法中有一個initApplicationEventMulticaster()方法,就是呼叫 AbstractApplicationContext.initApplicationEventMulticaster() 來初始化這個多播器(派發器)的:

    protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        //從bean工廠查詢有沒有一個bean為applicationEventMulticaster,如果有,從容器中拿出來
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            this.applicationEventMulticaster =
                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
            if (logger.isTraceEnabled()) {
                logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
            }
        }
        else {
            //如果沒有,則往容器中註冊一個SimpleApplicationEventMulticaster,名字為applicationEventMulticaster,如果派發事件需要就可以使用了
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
            if (logger.isTraceEnabled()) {
                logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
                        "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
            }
        }
    }   

  4.說完了  getApplicationEventMulticaster(),再來說說 multicastEvent(),它的作用是派發事件,它是 ApplicationEventMulticaster介面的一個方法,所以它會呼叫實現類 SimpleApplicationEventMul

ticaster(上面註冊的多播器)的multicastEvent 方法:

public interface ApplicationEventMulticaster {

    void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);

}
//public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();
        //根據訊息型別取出對應的所有監聽器
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            //如果可以使用多執行緒執行,就使用多執行緒來非同步派發事件,執行監聽器的方法
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }
}

  5.最後來看看 invokeListener 方法,它會拿到監聽器來回撥 MyApplicationListener. onApplicationEvent方法,然後控制檯就輸出了資訊 :

    protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        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();
            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.isTraceEnabled()) {
                    logger.trace("Non-matching event type for listener: " + listener, ex);
                }
            }
            else {
                throw ex;
            }
        }
    }

  6.當再執行案例2的事件時,從AbstractApplicationContext. publishEvent()(上面第3步)開始執行,步驟還是multicastEvent派發事件 --> invokeListener回撥執行 onApplicationEvent;

  7.當容器關閉時,會呼叫doClose(),裡面有個ContextClosedEvent事件,監聽器監聽到事件控制檯就輸出了資訊:

    context.close();
    @Override
    public void close() {
        synchronized (this.startupShutdownMonitor) {
            doClose();
            // If we registered a JVM shutdown hook, we don't need it anymore now:
            // We've already explicitly closed the context.
            if (this.shutdownHook != null) {
                try {
                    Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
                }
                catch (IllegalStateException ex) {
                    // ignore - VM is already shutting down
                }
            }
        }
    }
    protected void doClose() {
        // Check whether an actual close attempt is necessary...
        if (this.active.get() && this.closed.compareAndSet(false, true)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Closing " + this);
            }
            
            LiveBeansView.unregisterApplicationContext(this);

            try {
                // Publish shutdown event.
                publishEvent(new ContextClosedEvent(this));
            }
            catch (Throwable ex) {
                logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
            }

            // Stop all Lifecycle beans, to avoid delays during individual destruction.
            if (this.lifecycleProcessor != null) {
                try {
                    this.lifecycleProcessor.onClose();
                }
                catch (Throwable ex) {
                    logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
                }
            }

            // Destroy all cached singletons in the context's BeanFactory.
            destroyBeans();

            // Close the state of this context itself.
            closeBeanFactory();

            // Let subclasses do some final clean-up if they wish...
            onClose();

            // Reset local application listeners to pre-refresh state.
            if (this.earlyApplicationListeners != null) {
                this.applicationListeners.clear();
                this.applicationListeners.addAll(this.earlyApplicationListeners);
            }

            // Switch to inactive.
            this.active.set(false);
        }
    }

   8.上面在第4步中派發事件操作 getApplicationListeners 拿到了所有的監聽器,那麼容器中有哪些監聽器呢?從第1步的 registerListeners() 可以看到,容器是先註冊了多播器和監聽器後才進行事件的釋出,下面是監聽器的註冊:

    protected void registerListeners() {
        // Register statically specified listeners first.
        //從容器中拿到所有的監聽器新增到多路派發器中,(最開始是沒有的,跳過迴圈執行下面的步驟)
        for (ApplicationListener<?> listener : getApplicationListeners()) {
            getApplicationEventMulticaster().addApplicationListener(listener);
        }

        // Do not initialize FactoryBeans here: We need to leave all regular beans
        // uninitialized to let post-processors apply to them!
        //若容器中沒有,即上面的遍歷沒有執行,則根據型別獲取元件名稱,然後根據元件名稱獲取對應的元件加入到多路派發器中
        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
        for (String listenerBeanName : listenerBeanNames) {
            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);
            }
        }
    }

    下面再來看看 getApplicationListeners 方法,發現它是 AbstractApplicationEventMulticaster 類的一個方法,所以在開始詳解 getApplicationListeners 方法前,先來看看 AbstractApplicationEventMulticaster 類是一個起什麼作用的類。

     從上面類圖可以看到 AbstractApplicationEventMulticaster 它實現了幾個介面,它是一個事件派發器,上文第3點裡面講到的初始化派發器獲取的 SimpleApplicationEventMulticaster 其實是繼承自AbstractApplicationEventMulticaster 的。

    在看下面的獲取監聽器原始碼前,我們先思考一個問題:監聽器如何做到只監聽指定型別的訊息?如何要實現,有什麼方式?

    我們可以先猜測下:

      1) 註冊監聽器的時候,將監聽器和訊息型別繫結;(該論證可檢視下文 addApplicationListener 實現方法的解析)

      2) 廣播的時候,按照這條訊息的型別去找指定了該型別的監聽器,但不可能每條廣播都去所有監聽器裡面找一遍,應該是說廣播的時候會觸發一次監聽器和訊息的型別繫結;

     好了,接下來我們帶著這些問題和猜測詳細看看 getApplicationListeners 的原始碼:

public abstract class AbstractApplicationEventMulticaster
        implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
    //建立監聽器助手類,用於存放監聽器集合,引數是否是預過濾監聽器
    private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);
    //ListenerCacheKey是基於事件型別和源型別的類作為key用來儲存監聽器助手
    final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);

    @Nullable
    private ClassLoader beanClassLoader;//類載入器
    //互斥的監聽器助手類
    private Object retrievalMutex = this.defaultRetriever;
    //監聽器助手類(封裝一組特定目標監聽器的幫助類,允許有效地檢索預過濾的監聽器,此助手的例項按照時間型別和源型別快取)
    private class ListenerRetriever {
        //存放事件監聽器,有序、不可重複
        public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
        //存放事件監聽器bean名稱,有序,不可重複
        public final Set<String> applicationListenerBeans = new LinkedHashSet<>();
        //是否預過濾監聽器
        private final boolean preFiltered;

        public ListenerRetriever(boolean preFiltered) {
            this.preFiltered = preFiltered;
        }
        //獲取事件監聽器
        public Collection<ApplicationListener<?>> getApplicationListeners() {
            //建立一個指定大小的ApplicationListener監聽器List集合
            List<ApplicationListener<?>> allListeners = new ArrayList<>(
                    this.applicationListeners.size() + this.applicationListenerBeans.size());
            allListeners.addAll(this.applicationListeners);
            //如果存放監聽器bean name的集合不為空
            if (!this.applicationListenerBeans.isEmpty()) {
                //獲取IOC容器工廠類
                BeanFactory beanFactory = getBeanFactory();
                for (String listenerBeanName : this.applicationListenerBeans) {
                    try {
                        //獲取指定bean name的監聽器例項
                        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
                    }
                }
            }
            if (!this.preFiltered || !this.applicationListenerBeans.isEmpty()) {
                AnnotationAwareOrderComparator.sort(allListeners);
            }
            return allListeners;
        }
    }
    //流程1:當所釋出的事件型別和事件源型別與Map(retrieverCache)中的key匹配時,
    //將直接返回value中的監聽器列表作為匹配結果,通常這發生在事件不是第一次釋出時,能避免遍歷所有監聽器並進行過濾,
    //如果事件時第一次釋出,則會執行流程2。
    protected Collection<ApplicationListener<?>> getApplicationListeners(
        ApplicationEvent event, ResolvableType eventType) {
        //事件源,事件最初發生在其上的物件
        Object source = event.getSource();
        //事件源class物件
        Class<?> sourceType = (source != null ? source.getClass() : null);
        //快取的key有兩個維度:訊息來源+訊息型別(關於訊息來源可見ApplicationEvent構造方法的入參)
        //建立基於事件源和源型別的監聽器助手cacheKey
        ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

        // Quick check for existing entry on ConcurrentHashMap...
        // retrieverCache是ConcurrentHashMap物件(併發容器,使用鎖分段來確保多執行緒下資料安全),所以是執行緒安全的,
        // ListenerRetriever中有個監聽器的集合,並有些簡單的邏輯封裝,呼叫它的getApplicationListeners方法返回的監聽類集合是排好序的(order註解排序)
        ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
        //如果retrieverCache中找到對應的監聽器集合,就立即返回了
        if (retriever != null) {
            return retriever.getApplicationListeners();
        }
        //如果類載入器為null,或者事件源在給定的類載入器上下文是安全的並且源型別為null或者源型別在指定上下文是安全的
        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
            //鎖定監聽器助手物件,同步從ListenerRetriever監聽器助手中獲取指定的監聽器
            synchronized (this.retrievalMutex) {
                //搶到鎖之後再做一次判斷,因為有可能在前面BLOCK的時候,另一個搶到鎖的執行緒已經設定好了快取,
                //即避免自己在BLOCK的時候其他執行緒已經將資料放入快取了
                retriever = this.retrieverCache.get(cacheKey);
                if (retriever != null) {
                     //返回監聽器助手中儲存的監聽器物件
                    return retriever.getApplicationListeners();
                }
                retriever = new ListenerRetriever(true);
                //retrieveApplicationListeners方法實際檢索給定事件和源型別的監聽器
                Collection<ApplicationListener<?>> listeners =
                        retrieveApplicationListeners(eventType, sourceType, retriever);//流程2
                //retriever放到快取器中(更新快取)
                this.retrieverCache.put(cacheKey, retriever);
                return listeners;
            }
        }
        else {
            // No ListenerRetriever caching -> no synchronization necessary
            // 無ListenerRetriever監聽器助手 -> 無需同步快取
            return retrieveApplicationListeners(eventType, sourceType, null);
        }
    }
}

    從上面的原始碼可以看出:

      1)在獲取 ApplicationListener 的時候用到了快取,同時有快取更新和用鎖來確保執行緒同步(雙重判斷也做了),這樣如果在自定義廣播時,如果多執行緒同時發廣播,就不會有執行緒同步的問題了。  

      2)傳送訊息的時候根據型別去找所有對應的監聽器,這樣就可以實現自定義監聽器只接收指定型別的訊息。

        3)在廣播訊息的時刻,如果某個型別的訊息在快取中找不到對應的監聽器集合,就呼叫 retrieveApplicationListeners 方法去找出符合條件的所有監聽器,然後放入這個集合中。

    下面再詳細看看 retrieveApplicationListeners:

    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);
            //獲取監聽器助手中儲存的監聽器bean名稱集合,去重
            listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
        }

        // Add programmatically registered listeners, including ones coming
        // from ApplicationListenerDetector (singleton beans and inner beans).
        ////遍歷所有的監聽器
        for (ApplicationListener<?> listener : listeners) {
            //判斷監聽器是否匹配的邏輯在supportsEvent(listener, eventType, sourceType)中
            if (supportsEvent(listener, eventType, sourceType)) {
                //監聽器助手類不為空,加入監聽器助手類中的監聽器集合中
                if (retriever != null) {
                    retriever.applicationListeners.add(listener);
                }
                //放入匹配的監聽器列表中
                allListeners.add(listener);
            }
        }

        // Add listeners by bean name, potentially overlapping with programmatically
        // registered listeners above - but here potentially with additional metadata.
        //監聽器助手中儲存的監聽器bean名稱集合有值
        if (!listenerBeans.isEmpty()) {
            //獲取IOC容器
            ConfigurableBeanFactory beanFactory = getBeanFactory();
            //遍歷
            for (String listenerBeanName : listenerBeans) {
                try {
                    //判斷監聽器是否匹配的邏輯在supportsEvent(listener, eventType, sourceType)中
                    if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
                        //獲取指定bean name的監聽器例項
                        ApplicationListener<?> listener =
                                beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                        //如果匹配的監聽器列表不包含上面獲取的例項和符合supportsEvent的邏輯
                        if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
                            //監聽器助手類不為空
                            if (retriever != null) {
                                //bean名稱是單例,在容器中沒有重複監聽器
                                if (beanFactory.isSingleton(listenerBeanName)) {
                                    //加入監聽器助手類中的監聽器集合中
                                    retriever.applicationListeners.add(listener);
                                }
                                else {
                                    //去重,applicationListenerBeans是LinkedHashSet
                                    retriever.applicationListenerBeans.add(listenerBeanName);
                                }
                            }
                            //放入匹配的監聽器列表中
                            allListeners.add(listener);
                        }
                    }
                    //監聽器是否匹配的邏輯不在supportsEvent(listener, eventType, sourceType)中,從監聽器助手類和匹配的監聽器列表中移除
                    else {
                        // Remove non-matching listeners that originally came from
                        // ApplicationListenerDetector, possibly ruled out by additional
                        // BeanDefinition metadata (e.g. factory method generics) above.
                        Object listener = beanFactory.getSingleton(listenerBeanName);
                        if (retriever != null) {
                            retriever.applicationListeners.remove(listener);
                        }
                        allListeners.remove(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;
    }

     下面是 supportsEvent 的原始碼探究:

    //首先對原始的ApplicationListener進行一層介面卡包裝成GenericApplicationListener,
    //便於後面使用該介面中定義的方法判斷監聽器是否支援傳入的事件型別或事件源型別
    protected boolean supportsEvent(
            ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {

        GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
                (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
        return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
    }
    
    public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {  
        //判斷是否支援該事件型別
        boolean supportsEventType(ResolvableType eventType); 

        //判斷是否支援該事件源型別,預設是true,也就是說事件源的型別通常對於判斷匹配的監聽器沒有意義
        default boolean supportsSourceType(@Nullable Class<?> sourceType) {
            return true;
        }
    }
public class GenericApplicationListenerAdapter implements GenericApplicationListener, SmartApplicationListener {

    ......
    //監聽器泛型的實際型別
    @Nullable
    private final ResolvableType declaredEventType;

    
    //判斷監聽器是否支援該事件型別,因為我們的監聽器例項通常都不是SmartApplicationListener型別
    //eventType是釋出的事件的型別
    @Override
    @SuppressWarnings("unchecked")
    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 {
        //this.declaredEventType.isAssignableFrom(eventType)當以下兩種情況返回true
        //    1.declaredEventType和eventType型別相同
        //    2.declaredEventType是eventType的父型別
        //只要監聽器泛型的實際型別和釋出的事件型別一樣或是它的父型別,則該監聽器將被成功匹配。
            return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
        }
    }
}

    9.下面是流程總結圖:

 四.擴充套件

  1.從上面的案例中可以看到,只要 bean 繼承 ApplicationEvent,然後使用容器的 publishEvent 就可以釋出事件了(類似上文案例4),那麼還有其他的方式使 bean 具有跟容器一樣具有釋出事件的能力嗎?

    答案當然是有的,比如 ApplicationEventPublisherAware 這個介面就可以使 bean 具有釋出事件的能力。下面是該介面的原始碼:

public interface ApplicationEventPublisherAware extends Aware {

    void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher);

}

     我們可以建立一個bean,實現了 ApplicationEventPublisherAware 介面,那麼該 bean 的 setApplicationEventPublisher 方法就會被呼叫,通過該方法可以接收到 ApplicationEventPublisher 型別的入參,藉助這個 ApplicationEventPublisher 就可以發訊息了;

    比如下面的案例:

    1)介面:

public interface UserEventRegisterService {
    /**
     * 釋出事件,註冊使用者
     */
    void register();
}

    2)介面實現:

@Service
public class UserEventRegisterServiceImpl implements UserEventRegisterService {
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void register() {
        User user = new User();
        user.setId(1);
        user.setName("張三");
        user.setSex("男");
        applicationEventPublisher.publishEvent(user);
        System.out.println("結束。");
    }
}

    3)自定義監聽器:可以使用 @EventListener 註解來實現自定義監聽器(該註解下文詳細介紹)

@Component
public class UserEventListener {
    @EventListener(condition = "#user.id != null")//監聽當使用者id不為空的事件
    public void handleEvent(User user){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("監聽器監聽到的資訊:"+user);
    }
}

    4)配置類:

@ComponentScan("com.hrh.ext")
@Configuration
public class ExtConfig {

}

    5)測試:

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ExtConfig.class);
        UserEventRegisterService service = context.getBean(UserEventRegisterService.class);
        service.register();
        context.close();
    }
======執行結果======
監聽器監聽到的資訊:User [id=1, name=張三, sex=男]
結束。

  2.監聽器是如何加入到容器中的呢?我們從容器 refresh 重新整理開始看起,發現在 refresh 裡面有 prepareBeanFactory(beanFactory) 方法,為所有bean準備了一個後置處理器 ApplicationListenerDetector:

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

            .........
        }
}
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    ..........

    // Register early post-processor for detecting inner beans as ApplicationListeners.
    beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

    ..........
}

    接下來再看看 ApplicationListenerDetector類的原始碼,由於它是一個後置處理器(不清楚後置處理器的可檢視上篇文章,開頭有簡單介紹),所以它有 postProcessBeforeInitialization 和 postProcessAfterInitialization 兩個方法,這兩個方法在對所有的 bean 進行例項化後進行攔截操作:

    private final transient AbstractApplicationContext applicationContext;
    
    private final transient Map<String, Boolean> singletonNames = new ConcurrentHashMap<>(256);
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        //如果bean是ApplicationListener型別
        if (bean instanceof ApplicationListener) {
            // potentially not detected as a listener by getBeanNamesForType retrieval
            //判斷bean在不在併發容器中
            Boolean flag = this.singletonNames.get(beanName);
            if (Boolean.TRUE.equals(flag)) {
                // singleton bean (top-level or inner): register on the fly
                //註冊監聽器,其實就是儲存在成員變數applicationEventMulticaster的成員變數defaultRetriever的集合applicationListeners中
                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;
    }
    public void addApplicationListener(ApplicationListener<?> listener) {
        Assert.notNull(listener, "ApplicationListener must not be null");
        if (this.applicationEventMulticaster != null) {
            this.applicationEventMulticaster.addApplicationListener(listener);
        }
        this.applicationListeners.add(listener);
    }    

    如上所示,如果當前 bean 實現了 ApplicationListener 介面,就會呼叫 this.applicationContext.addApplicationListener 方法將當前 bean 註冊到 applicationContext 的監聽器集合中,後面有廣播就直接找到這些監聽器,呼叫每個監聽器的 onApplicationEvent 方法;  

    接下來詳細看看 addApplicationListener 方法,它的實現方法在 AbstractApplicationEventMulticaster 類中:

@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) {
            //如果因為AOP導致建立了監聽類的代理,那麼就要在註冊列表中清除代理類
            this.defaultRetriever.applicationListeners.remove(singletonTarget);
        }
        //把監聽器加入集合defaultRetriever.applicationListeners中,這是個LinkedHashSet例項
        this.defaultRetriever.applicationListeners.add(listener);
        //清空監聽器助手快取Map
        this.retrieverCache.clear();
    }
}

     在上面可以看到,如果物件是由 AOP生成的代理類,需要清除,是因為 AOP 是通過代理技術實現的,此時可能通過 CGLIB 生成了監聽類的代理類,此類的例項如果被註冊到監聽器集合中,那麼廣播時按照訊息型別就會取出兩個監聽器例項來,到時就是一個訊息被兩個例項消費了,因此需要先清理掉代理類。

    同時也論證了一個觀點:所謂的註冊監聽器,其實就是把 ApplicationListener 的實現類放入一個LinkedHashSet的集合,此處沒有任何與訊息型別相關的操作,因此,監聽器註冊的時候並沒有將訊息型別和監聽器繫結

  3.非同步監聽事件案例:正常的事件通知是 ContextRefreshedEvent --> EmailEvent --> ContextClosedEvent

//開啟非同步支援
@EnableAsync
@Component
public class EmailListener implements ApplicationListener {
    @Async//非同步執行
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof EmailEvent) {

            EmailEvent emailEvent = (EmailEvent) event;
            System.out.println("郵件地址:" + emailEvent.getAddress());
            System.out.println("郵件內容:" + emailEvent.getText());
        } else {
            System.out.println("容器本身事件:" + event);
        }
        //通過執行緒名稱區別
        System.out.println(event+ ":" + Thread.currentThread().getName());
    }
}
=======測試執行結果:從下面的執行結果可以看出是非同步執行了=======
郵件地址:249968839@qq.com
郵件內容:This is a event test
com.hrh.ext.EmailEvent[source=hello]:SimpleAsyncTaskExecutor-2
容器本身事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e5d6d97, started on Mon Nov 09 22:29:09 CST 2020]
容器本身事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e5d6d97, started on Mon Nov 09 22:29:09 CST 2020]
org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e5d6d97, started on Mon Nov 09 22:29:09 CST 2020]:SimpleAsyncTaskExecutor-1
org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e5d6d97, started on Mon Nov 09 22:29:09 CST 2020]:SimpleAsyncTaskExecutor-3

   5.Spring事務監聽機制---使用 @TransactionalEventListener 處理資料庫事務提交成功後再執行操作

    在專案中,往往需要執行資料庫操作後,傳送訊息或事件來非同步呼叫其他元件執行相應的操作,例如:使用者註冊後傳送啟用碼、配置修改後傳送更新事件等。

    但是,資料庫的操作如果還未完成,此時非同步呼叫的方法查詢資料庫發現沒有資料,這就會出現問題。

    如下面虛擬碼案例: 

void saveUser(User u) {
    //儲存使用者資訊
    userDao.save(u);
    //觸發儲存使用者事件
    applicationContext.publishEvent(new SaveUserEvent(u.getId()));
}

@EventListener
void onSaveUserEvent(SaveUserEvent event) {
    //獲取事件中的資訊(使用者id)
    Integer id = event.getEventData();
    //查詢資料庫,獲取使用者(此時如果使用者還未插入資料庫,則返回空)
    User u = userDao.getUserById(id);
    //這裡可能報空指標異常!
    String phone = u.getPhoneNumber();
    MessageUtils.sendMessage(phone);
}

         為了解決上述問題,Spring為我們提供了兩種方式: @TransactionalEventListener 註解 和 事務同步管理器 TransactionSynchronizationManager,以便我們可以在事務提交後再觸發某一事件。

    @TransactionalEventListener 註解的使用案例:只有當前事務提交之後,才會執行事件監聽器的方法

//phase預設為AFTER_COMMIT,共有四個列舉:BEFORE_COMMIT,AFTER_COMMIT,AFTER_ROLLBACK,AFTER_COMPLETION
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) void onSaveUserEvent(SaveUserEvent event) { Integer id = event.getEventData(); User u = userDao.getUserById(id); String phone = u.getPhoneNumber(); MessageUtils.sendMessage(phone); }

    TransactionSynchronizationManager 的使用案例:@TransactionalEventListener底層下面這樣來實現的

@EventListener
void onSaveUserEvent(SaveUserEvent event) {
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
        @Override
        public void afterCommit() {
            Integer id = event.getEventData();
            User u = userDao.getUserById(id);
            String phone = u.getPhoneNumber();
            MessageUtils.sendMessage(phone);
        }
    });
}

相關文章