SpringBoot事件監聽器原始碼分析

bei_er發表於2022-07-10

本文涉及到Spring的監聽器,如果不太瞭解請先閱讀之前的Spring監聽器的文章。

SpringBoot事件監聽器初始化

SpringBoot中預設定義了11個事件監聽器物件,全部定義在META-INF/spring.factories檔案中。分別是:

org.springframework.boot.ClearCachesApplicationListener
org.springframework.boot.builder.ParentContextCloserApplicationListener
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor
org.springframework.boot.context.FileEncodingApplicationListener
org.springframework.boot.context.config.AnsiOutputApplicationListener
org.springframework.boot.context.config.ConfigFileApplicationListener
org.springframework.boot.context.config.DelegatingApplicationListener
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener
org.springframework.boot.context.logging.LoggingApplicationListener
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
org.springframework.boot.autoconfigure.BackgroundPreinitializer

在SpringApplication的構造方法中會對這些預設的事件監聽器進行例項化並且給this.listeners屬性賦值。程式碼如下:

// 從spring.factories 檔案中獲取 ApplicationListener 監聽器的實現類
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

SpringApplicationRunListeners 物件初始化,在SpringApplication.run()方法中會根據spring.factories檔案中配置的SpringApplicationRunListener介面和已經建立好的SpringApplication物件來建立SpringApplicationRunListener 物件,最終會賦給SpringApplicationRunListeners 物件的this.listeners屬性。程式碼如下:

// 生成事件釋出物件
// 建立 SpringApplicationRunListeners 並把 預設的事件監聽器賦值給 該物件
// 從 spring.factories檔案中載入 SpringApplicationRunListener 的介面實現類
// 具體載入的是 EventPublishingRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);

SpringApplicationRunListener 在spring.factories檔案中配置為EventPublishingRunListener物件。程式碼如下:

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

EventPublishingRunListener 中會建立具體的多播器物件SimpleApplicationEventMulticaster,會把SpringApplication物件賦值給this.application屬性,會把SpringApplication物件中的事件監聽器集合遍歷賦值給多播器裡面的事件監聽器集合。程式碼如下:

public EventPublishingRunListener(SpringApplication application, String[] args) {
    this.application = application;
    this.args = args;
    // 建立多播器
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
    for (ApplicationListener<?> listener : application.getListeners()) {
        this.initialMulticaster.addApplicationListener(listener);
    }
}

至此,SpringBoot的事件監聽釋出器初始化完成。

SpringBoot事件監聽器呼叫過程

整個SpringBoot的事件監聽器呼叫過程散佈在整個SpringBoot生命週期過程中。總共有8個釋出事件的方法,在8個不同的地方呼叫。

starting事件:在SpringBoot開始建立context之前會呼叫SpringApplicationRunListeners的starting()方法,釋出starting事件,在EventPublishingRunListener中程式碼如下:

@Override
public void starting() {
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

在多播器中找到所有符合事件型別的事件監聽器物件進行快取。程式碼如下,

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

getApplicationListeners中會根據事件監聽器物件和事件型別在多播器的監聽器物件中查詢滿足條件的事件監聽器物件,並進行快取

invokeListener 先判斷是否有設定錯誤處理程式,如果有則需要用錯誤處理程式來處理事件監聽器中發生的異常。

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    // 獲取此多播器的當前錯誤處理程式
    ErrorHandler errorHandler = getErrorHandler();
    // 如果errorHandler不為null
    if (errorHandler != null) {
        try {
            // 回撥listener的onApplicationEvent方法,傳入event
            doInvokeListener(listener, event);
        }
        catch (Throwable err) {
            // 交給errorHandler接收處理err
            errorHandler.handleError(err);
        }
    }
    else {
        // 回撥listener的onApplicationEvent方法,傳入event
        doInvokeListener(listener, event);
    }
}

doInvokeListener具體呼叫listener的onApplicationEvent方法,傳入event。

/**
* 回撥listener的onApplicationEvent方法,傳入 event
* @param listener
* @param event
*/
@SuppressWarnings({"rawtypes", "unchecked"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        //回撥listener的onApplicationEvent方法,傳入	        			event:contextrefreshListener:onapplicaitonEvent:FrameworkServlet.this.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;
        }
    }
}

environmentPrepared事件:環境變數準備事件,在建立好環境變數之後進行釋出。在EventPublishingRunListener中程式碼如下,

public void environmentPrepared(ConfigurableEnvironment environment) {
    this.initialMulticaster
        .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}

contextPrepared事件:應用程式上下文準備事件,在建立好ApplicationContext之後立馬呼叫。在EventPublishingRunListener中程式碼如下

public void contextPrepared(ConfigurableApplicationContext context) {
    this.initialMulticaster
        .multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}

contextLoaded事件:應用程式上下文載入事件,在主配置類註冊到ApplicationContext中之後會進行呼叫。在進行釋出ApplicationPreparedEvent事件之前,先把EventPublishingRunListener 的監聽器物件都注入到ApplicationContext中。在EventPublishingRunListener中程式碼如下

public void contextLoaded(ConfigurableApplicationContext context) {
    for (ApplicationListener<?> listener : this.application.getListeners()) {
        if (listener instanceof ApplicationContextAware) {
            ((ApplicationContextAware) listener).setApplicationContext(context);
        }
        context.addApplicationListener(listener);
    }
    this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}

started事件:應用程式啟動完成事件,在應用程式完成refresh之後進行呼叫。在EventPublishingRunListener中程式碼如下

public void started(ConfigurableApplicationContext context) {
    context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
}

running事件:應用程式上下文ApplicationContext物件建立好之後進行釋出該事件。在EventPublishingRunListener中程式碼如下

@Override
public void running(ConfigurableApplicationContext context) {
    context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
}

failed事件:在整個SpringBoot物件的生命週期過程,如果出現異常則釋出該事件。在EventPublishingRunListener中程式碼如下

public void failed(ConfigurableApplicationContext context, Throwable exception) {
    ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
    if (context != null && context.isActive()) {
        // Listeners have been registered to the application context so we should
        // use it at this point if we can
        context.publishEvent(event);
    }
    else {
        // An inactive context may not have a multicaster so we use our multicaster to
        // call all of the context's listeners instead
        if (context instanceof AbstractApplicationContext) {
            for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
                 .getApplicationListeners()) {
                this.initialMulticaster.addApplicationListener(listener);
            }
        }
        this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
        this.initialMulticaster.multicastEvent(event);
    }
}

SpringBoot事件監聽器和Spring的整合

一般來說我們建立事件監聽器只會只用Spring的建立方式,不會用SpringBoot的方式。但是通過程式碼發現,通過SpringBoot方式建立的事件監聽器會自動注入到Spring容器的多播器中。這是怎麼做到?一起來分析下。

兩種方式的區別

SpringBoot方式:事件監聽器只能通過實現ApplicationListener介面並且通過在META-INF/spring.factories檔案新增配置進行建立。建立之後會存在SpringApplicationRunListener物件的事件多播器中,並且多播器是SimpleApplicationEventMulticaster型別,不能進行重寫,是同步方式的多播器。

Spring方式:事件監聽器可以通過實現ApplicationListener介面或者@EventListener註解來實現,然後使用Spring的注入@Component註解即可建立。Spring的多播器預設是SimpleApplicationEventMulticaster型別,可以對Spring容器的多播器進行重寫。

整合原理

SpringBoot先建立自己的事件釋出器物件SpringApplicationRunListeners ,並且在整個生命週期中釋出不同事件,然後再把其中的事件監聽器新增到應用程式上下文ApplicationContext(Spring容器)中。Spring容器在之後初始化的過程中會把這些事件監聽器載入到Spring的多播器中,這樣就完成了整合。在SpringBoot啟動的生命週期過程中,有兩個地方可以對應用程式上下文ApplicationContext進行監聽器的建立。

  1. 在Spring Application的run()中的prepareContext()方法中,會執行applyInitializers方法。
// 初始化 context,在具體的 ApplicationContextInitializer 類中給 context 新增相關的事件監聽器
applyInitializers(context);

在該方法中會遍歷執行SpringApplication中建立的ApplicationContextInitializer介面物件。其中RSocketPortInfoApplicationContextInitializer物件和RSocketPortInfoApplicationContextInitializer物件以及ConditionEvaluationReportLoggingListener物件都會對ApplicationContext新增事件監聽器。

  1. 在Spring Application的run()中的prepareContext()方法中,會執行listeners.contextLoaded()方法。
// 釋出 contextLoaded 事件 ,把 SpringApplication 中的事件監聽器 新增給 context 的監聽器列表
listeners.contextLoaded(context);

該方法中會先把EventPublishingRunListener 中的監聽器物件都注入到ApplicationContext中,再進行釋出ApplicationPreparedEvent事件。具體程式碼在EventPublishingRunListener程式碼如下,

public void contextLoaded(ConfigurableApplicationContext context) {
    for (ApplicationListener<?> listener : this.application.getListeners()) {
        if (listener instanceof ApplicationContextAware) {
            ((ApplicationContextAware) listener).setApplicationContext(context);
        }
        context.addApplicationListener(listener);
    }
    this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}

相關文章