頭禿了,二十三張圖帶你從源碼瞭解Spring Boot 的啓動流程~

持續原創輸出,點擊上方藍字關注我

目錄

  • 前言
  • 源碼版本
  • 從哪入手?
  • 源碼如何切分?
  • 如何創建SpringApplication?
    • 設置應用類型
    • 設置初始化器(Initializer)
    • 設置監聽器(Listener)
    • 設置監聽器(Listener)
  • 執行run()方法
    • 獲取、啓動運行過程監聽器
    • 環境構建
    • 創建IOC容器
    • IOC容器的前置處理
    • 刷新容器
    • IOC容器的後置處理
    • 發出結束執行的事件
    • 執行Runners
    • 總結
  • 總結

前言

Spring Boot 專欄已經寫了五十多天了,前面二十章從基礎應用到高級整合避重就輕介紹的都是工作、面試中常見的知識點。

今天開始底層源碼介紹的階段,相對內容比較深一點,作者也儘可能介紹的通俗易懂,層次分明一點。相信讀過我寫的Mybatis專欄的文章都知道,只要跟着作者的步驟,方法一步步研究,其實源碼並不難。

這篇文章花了四天時間精雕細琢,力求介紹的通俗易懂,畢竟源碼相對難度更高些,希望通過作者拆分講解能夠幫助到讀者。

如果沒讀過作者的前二十篇文章,點擊前往

源碼版本

作者Spring Boot是基於2.4.0。每個版本有些變化,讀者儘量和我保持一致,以防源碼有些出入。

從哪入手?

相信很多人嘗試讀過Spring Boot的源碼,但是始終沒有找到合適的方法。那是因爲你對Spring Boot的各個組件、機制不是很瞭解,研究起來就像大海撈針。

至於從哪入手不是很簡單的問題嗎,當然主啓動類了,即是標註着@SpringBootApplication註解並且有着main()方法的類,如下一段代碼:

@SpringBootApplication
public class AnnotationDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(AnnotationDemoApplication.classargs);
    }
}

話不多說,DEBUG伺候,別怕,搞它........

源碼如何切分?

SpringApplication中的靜態run()方法並不是一步完成的,最終執行的源碼如下:

//org.springframework.context.ConfigurableApplicationContext
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
  return new SpringApplication(primarySources).run(args);
 }

很顯然分爲兩個步驟,分別是創建SpringApplication和執行run()方法,下面將分爲這兩個部分介紹。

如何創建SpringApplication?

創建即是new對象了,DEBUG跟進代碼,最終執行的SpringApplication構造方法如下圖:

如上圖中標註的註釋,創建過程重用的其實分爲這三個階段,下面將會一一介紹每個階段做了什麼事。

設置應用類型

這個過程非常重要,直接決定了項目的類型,應用類型分爲三種,都在WebApplicationType這個枚舉類中,如下:

  1. NONE:顧名思義,什麼都沒有,正常流程走,不額外的啓動web容器,比如Tomcat
  2. SERVLET:基於servlet的web程序,需要啓動內嵌的servletweb容器,比如Tomcat
  3. REACTIVE:基於reactive的web程序,需要啓動內嵌reactiveweb容器,作者不是很瞭解,不便多說。

判斷的依據很簡單,就是加載對應的類,比如加載了DispatcherServlet等則會判斷是Servlet的web程序。源碼如下:

static WebApplicationType deduceFromClasspath() {
  if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
   return WebApplicationType.REACTIVE;
  }
  for (String className : SERVLET_INDICATOR_CLASSES) {
   if (!ClassUtils.isPresent(className, null)) {
    return WebApplicationType.NONE;
   }
  }
  return WebApplicationType.SERVLET;
 }

這裏我引入了spring-boot-starter-web,肯定是Servlet的web程序。

設置初始化器(Initializer)

初始化器ApplicationContextInitializer是個好東西,用於IOC容器刷新之前初始化一些組件,比如ServletContextApplicationContextInitializer

那麼如何獲取初始化器呢?跟着上圖中的代碼進入,在SpringApplication中的如下圖中的方法:

相對重要的就是第一步獲取初始化器的名稱了,這個肯定是全類名了,詳細源碼肯定在loadFactoryNames()方法中了,跟着源碼進入,最終調用的是#SpringFactoriesLoader.loadSpringFactories()方法。

loadSpringFactories()方法就不再詳細解釋了,其實就是從類路徑META-INF/spring.factories中加載ApplicationContextInitializer的值。

spring-boot-autoconfigurespring.factories文件中的值如下圖:

上圖中的只是一部分初始化器,因爲spring.factories文件不止一個。

下圖中是我的demo中注入的初始化器,現實項目中並不止這些。

這也告訴我們自定義一個ApplicationContextInitializer只需要實現接口,在spring.factories文件中設置即可。

設置監聽器(Listener)

監聽器(ApplicationListener)這個概念在Spring中就已經存在,主要用於監聽特定的事件(ApplicationEvent),比如IOC容器刷新、容器關閉等。

Spring Boot擴展了ApplicationEvent構建了SpringApplicationEvent這個抽象類,主要用於Spring Boot啓動過程中觸發的事件,比如程序啓動中、程序啓動完成等。如下圖:

監聽器如何獲取?從源碼中知道其實和初始化器(ApplicationContextInitializer)執行的是同一個方法,同樣是從META-INF/spring.factories文件中獲取。

spring-boot-autoconfigurespring.factories文件中的值如下圖:

spring.factories文件不止一個,同樣監聽器也不止以上這些。

作者demo中注入的一些監聽器如下圖:

總結

SpringApplication的構建都是爲了run()方法啓動做鋪墊,構造方法中總共就有幾行代碼,最重要的部分就是設置應用類型、設置初始化器、設置監聽器。

注意:初始化器和這裏的監聽器都要放置在spring.factories文件中才能在這一步驟加載,否則不會生效,因此此時IOC容器還未創建,即使將其注入到IOC容器中也是不會生效的。

作者簡單的畫了張執行流程圖,僅供參考,如下:

執行run()方法

上面分析了SpringApplication的構建過程,一切都做好了鋪墊,現在到了啓動的過程了。

作者根據源碼將啓動過程分爲了8步,下面將會一一介紹。

1. 獲取、啓動運行過程監聽器

SpringApplicationRunListener這個監聽器和ApplicationListener不同,它是用來監聽應用程序啓動過程的,接口的各個方法含義如下:

public interface SpringApplicationRunListener {

    // 在run()方法開始執行時,該方法就立即被調用,可用於在初始化最早期時做一些工作
    void starting();
    // 當environment構建完成,ApplicationContext創建之前,該方法被調用
    void environmentPrepared(ConfigurableEnvironment environment);
    // 當ApplicationContext構建完成時,該方法被調用
    void contextPrepared(ConfigurableApplicationContext context);
    // 在ApplicationContext完成加載,但沒有被刷新前,該方法被調用
    void contextLoaded(ConfigurableApplicationContext context);
    // 在ApplicationContext刷新並啓動後,CommandLineRunners和ApplicationRunner未被調用前,該方法被調用
    void started(ConfigurableApplicationContext context);
    // 在run()方法執行完成前該方法被調用
    void running(ConfigurableApplicationContext context);
    // 當應用運行出錯時該方法被調用
    void failed(ConfigurableApplicationContext context, Throwable exception);
}

如何獲取運行監聽器?

SpringApplication#run()方法中,源碼如下:

//從spring.factories中獲取監聽器
SpringApplicationRunListeners listeners = getRunListeners(args);

跟進getRunListeners()方法,其實還是調用了loadFactoryNames()方法從spring.factories文件中獲取值,如下:

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

最終注入的是EventPublishingRunListener這個實現類,創建實例過程肯定是通過反射了,因此我們看看它的構造方法,如下圖:

這個運行監聽器內部有一個事件廣播器(SimpleApplicationEventMulticaster),主要用來廣播特定的事件(SpringApplicationEvent)來觸發特定的監聽器ApplicationListener

EventPublishingRunListener中的每個方法用來觸發SpringApplicationEvent中的不同子類。

如何啓動運行監聽器?

SpringApplication#run()方法中,源碼如下:

//執行starting()方法
listeners.starting(bootstrapContext, this.mainApplicationClass);

執行SpringApplicationRunListenersstarting()方法,跟進去其實很簡單,遍歷執行上面獲取的運行監聽器,這裏只有一個EventPublishingRunListener。因此執行的是它的starting()方法,源碼如下圖:

上述源碼中邏輯很簡單,其實只是執行了multicastEvent()方法,廣播了ApplicationStartingEvent事件。至於multicastEvent()內部方法感興趣的可以看看,其實就是遍歷ApplicationListener的實現類,找到監聽ApplicationStartingEvent這個事件的監聽器,執行onApplicationEvent()方法。

總結

這一步其實就是廣播了ApplicationStartingEvent事件來觸發監聽這個事件的ApplicationListener

因此如果自定義了ApplicationListener並且監聽了ApplicationStartingEvent(應用程序開始啓動)事件,則這個監聽器將會被觸發。

2. 環境構建

這一步主要用於加載系統配置以及用戶的自定義配置(application.properties),源碼如下,在run()方法中:

ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

prepareEnvironment方法內部廣播了ApplicationEnvironmentPreparedEvent事件,源碼如下圖:

環境構建這一步加載了系統環境配置、用戶自定義配置並且廣播了ApplicationEnvironmentPreparedEvent事件,觸發監聽器。

3. 創建IOC容器

源碼在run()方法中,如下:

context = createApplicationContext();

跟進代碼,真正執行的是ApplicationContextFactory方法,如下圖:

根據webApplicationType決定創建的類型,很顯然,我這裏的是servlet,因此創建的是AnnotationConfigServletWebServerApplicationContext

這一步僅僅是創建了IOC容器,未有其他操作。

4. IOC容器的前置處理

這一步真是精華了,在刷新容器之前做準備,其中有一個非常關鍵的操作:將啓動類注入容器,爲後續的自動化配置奠定基礎。源碼如下:

prepareContext(context, environment, listeners, applicationArguments,printedBanner);

prepareContext()源碼解析如下圖,內容還是挺多的:

從上圖可以看出步驟很多,下面將會詳細介紹幾個重點的內容。

調用初始化器

SpringApplication構建過程中設置的初始化器,從spring.factories取值的。執行的流程很簡單,遍歷執行,源碼如下圖:

將自定義的ApplicationContextInitializer放在META-INF/spring.factories中,在此時也是會被調用。

加載啓動類,注入容器

這一步是將主啓動類加載到IOC容器中,作爲後續自動配置的入口。

SpringApplication構建過程中將主啓動類放置在primarySources這個集合中,此時的getAllSources()即是從其中取值,如下圖:

這裏取出的就是主啓動類,當然你的項目中可能不止一個,接下來就是將其加載到IOC容器中了,源碼如下:

load(context, sources.toArray(new Object[0]));

跟着代碼進去,其實主要邏輯都在BeanDefinitionLoader.load()方法,如下圖:

將主啓動類加載到beanDefinitionMap,後續該啓動類將作爲開啓自動配置化的入口,後續章節詳細介紹。

兩次廣播事件

這一步涉及到了兩次事件廣播,分別是ApplicationContextInitializedEventApplicationPreparedEvent,對應的源碼如下:

listeners.contextPrepared(context);
load(context, sources.toArray(new Object[0]));

5. 刷新容器

刷新容器完全是Spring的功能了,比如初始化資源,初始化上下文廣播器等,這個就不再詳細介紹,有興趣可以看看Spring的源碼。

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.classapplicationContext);
    //調用創建的容器applicationContext中的refresh()方法
    ((AbstractApplicationContext)applicationContext).refresh();
}
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        /**
         * 刷新上下文環境
         */

        prepareRefresh();

        /**
         * 初始化BeanFactory,解析XML,相當於之前的XmlBeanFactory的操作,
         */

        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        /**
         * 爲上下文準備BeanFactory,即對BeanFactory的各種功能進行填充,如常用的註解@Autowired @Qualifier
         * 添加ApplicationContextAwareProcessor處理器
         * 在依賴注入忽略實現*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
         * 註冊依賴,如一個bean的屬性中含有ApplicationEventPublisher(beanFactory),則會將beanFactory的實例注入進去
         */

        prepareBeanFactory(beanFactory);

        try {
            /**
             * 提供子類覆蓋的額外處理,即子類處理自定義的BeanFactoryPostProcess
             */

            postProcessBeanFactory(beanFactory);

            /**
             * 激活各種BeanFactory處理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
             * 執行對應的postProcessBeanDefinitionRegistry方法 和  postProcessBeanFactory方法
             */

            invokeBeanFactoryPostProcessors(beanFactory);

            /**
             * 註冊攔截Bean創建的Bean處理器,即註冊BeanPostProcessor,不是BeanFactoryPostProcessor,注意兩者的區別
             * 注意,這裏僅僅是註冊,並不會執行對應的方法,將在bean的實例化時執行對應的方法
             */

            registerBeanPostProcessors(beanFactory);

            /**
             * 初始化上下文中的資源文件,如國際化文件的處理等
             */

            initMessageSource();

            /**
             * 初始化上下文事件廣播器,並放入applicatioEventMulticaster,如ApplicationEventPublisher
             */

            initApplicationEventMulticaster();

            /**
             * 給子類擴展初始化其他Bean
             */

            onRefresh();

            /**
             * 在所有bean中查找listener bean,然後註冊到廣播器中
             */

            registerListeners();

            /**
             * 設置轉換器
             * 註冊一個默認的屬性值解析器
             * 凍結所有的bean定義,說明註冊的bean定義將不能被修改或進一步的處理
             * 初始化剩餘的非惰性的bean,即初始化非延遲加載的bean
             */

            finishBeanFactoryInitialization(beanFactory);

            /**
             * 通過spring的事件發佈機制發佈ContextRefreshedEvent事件,以保證對應的監聽器做進一步的處理
             * 即對那種在spring啓動後需要處理的一些類,這些類實現了ApplicationListener<ContextRefreshedEvent>,
             * 這裏就是要觸發這些類的執行(執行onApplicationEvent方法)
             * 另外,spring的內置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
             * 完成初始化,通知生命週期處理器lifeCycleProcessor刷新過程,同時發出ContextRefreshEvent通知其他人
             */

            finishRefresh();
        }

        finally {
    
            resetCommonCaches();
        }
    }
}

6. IOC容器的後置處理

一個擴展方法,源碼如下:

afterRefresh(context, applicationArguments);

默認爲空,如果有自定義需求可以重寫,比如打印一些啓動結束日誌等。

7. 發出結束執行的事件

同樣是EventPublishingRunListener這個監聽器,廣播ApplicationStartedEvent事件。

但是這裏廣播事件和前幾次不同,並不是廣播給SpringApplication中的監聽器(在構建過程中從spring.factories文件獲取的監聽器)。因此在IOC容器中注入的監聽器(使用@Component等方式注入的)也能夠生效。前面幾個事件只有在spring.factories文件中設置的監聽器纔會生效。

跟着代碼進入,可以看到started()方法源碼如下:

這裏並沒有用事件廣播器SimpleApplicationEventMulticaster廣播事件,而是使用ConfigurableApplicationContext直接在IOC容器中發佈事件。

8. 執行Runners

Spring Boot 提供了兩種Runner讓我們定製一些額外的操作,分別是CommandLineRunnerApplicationRunner,關於這兩個的區別,後面文章詳細介紹。

調用的源碼如下:

callRunners(context, applicationArguments);

跟進代碼,其實真正調執行的是如下方法:

邏輯很簡單,從IOC容器中獲取,遍歷調用。

總結

Spring Boot 啓動流程相對簡單些,作者將其細分了以上八個步驟,希望能夠幫助讀者理解,流程圖如下:

總結

Spring Boot啓動流程就介紹到這裏了,需要重點理解run()方法執行的八個步驟以及事件、初始化器、監聽器等組件的執行時間點。

作者每一篇文章都很用心,這篇源碼解析花了三天時間精雕細琢,力求講解的通俗易懂,希望能夠幫助到你。

另外作者的第一本PDF書籍已經整理好了,由淺入深的詳細介紹了Mybatis基礎以及底層源碼,有需要的朋友公號回覆關鍵詞Mybatis進階即可獲取,目錄如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章