天天看點

SpringBoot啟動過程源碼解析

作者:菜鳥程式院

構造SpringApplication對象

1、推測web應用類型

  1. 如果項目依賴中存在org.springframework.web.reactive.DispatcherHandler,并且不存在org.springframework.web.servlet.DispatcherServlet,那麼應用類型為WebApplicationType.REACTIVE
  2. 如果項目依賴中不存在org.springframework.web.reactive.DispatcherHandler,也不存在org.springframework.web.servlet.DispatcherServlet,那麼應用類型為WebApplicationType.NONE
  3. 否則,應用類型為WebApplicationType.SERVLET

2、擷取BootstrapRegistryInitializer對象

  1. 從"META-INF/spring.factories"中讀取key為BootstrapRegistryInitializer類型的擴充點,并執行個體化出對應擴充點對象
  2. BootstrapRegistryInitializer的作用是可以初始化BootstrapRegistry
  3. 上面的DefaultBootstrapContext對象就是一個BootstrapRegistry,可以用來注冊一些對象,這些對象可以用在從SpringBoot啟動到Spring容器初始化完成的過程中
  4. 我的了解:沒有Spring容器之前就利用BootstrapRegistry來共享一些對象,有了Spring容器之後就利用Spring容器來共享一些對象

3、擷取ApplicationContextInitializer對象

  1. 從"META-INF/spring.factories"中讀取key為ApplicationContextInitializer類型的擴充點,并執行個體化出對應擴充點對象
  2. 顧名思義,ApplicationContextInitializer是用來初始化Spring容器ApplicationContext對象的,比如可以利用ApplicationContextInitializer來向Spring容器中添加ApplicationListener

4、擷取ApplicationListener對象

  1. 從"META-INF/spring.factories"中讀取key為ApplicationListener類型的擴充點,并執行個體化出對應擴充點對象
  2. ApplicationListener是Spring中的監聽器,并不是SpringBoot中的新概念,不多解釋了

5、推測出Main類(main()方法所在的類)

沒什麼具體的作用,邏輯是根據目前線程的調用棧來判斷main()方法在哪個類,哪個類就是Main類

run(String... args)方法

  1. 建立DefaultBootstrapContext對象
  2. 利用BootstrapRegistryInitializer初始化DefaultBootstrapContext對象
  3. 擷取SpringApplicationRunListeners

這三個步驟沒什麼特殊的

5、觸發SpringApplicationRunListener的starting()

預設情況下SpringBoot提供了一個EventPublishingRunListener,它實作了SpringApplicationRunListener接口,預設情況下會利用EventPublishingRunListener釋出一個ApplicationContextInitializedEvent事件,程式員可以通過定義ApplicationListener來消費這個事件

6、建立Environment對象

Environment對象表示環境變量,該對象内部主要包含了:

  1. 目前作業系統的環境變量
  2. JVM的一些配置資訊
  3. -D方式所配置的JVM環境變量

7、觸發SpringApplicationRunListener的environmentPrepared()

預設情況下會利用EventPublishingRunListener釋出一個ApplicationEnvironmentPreparedEvent事件,程式員可以通過定義ApplicationListener來消費這個事件,比如預設情況下會有一個EnvironmentPostProcessorApplicationListener來消費這個事件,而這個ApplicationListener接收到這個事件之後,就會解析application.properties、application.yml檔案,并添加到Environment對象中去。

8、列印Banner

9、建立Spring容器對象(ApplicationContext)

會利用ApplicationContextFactory.DEFAULT,根據應用類型建立對應的Spring容器。

ApplicationContextFactory.DEFAULT為:

ApplicationContextFactory DEFAULT = (webApplicationType) -> {
    try {
        switch (webApplicationType) {
            case SERVLET:
                return new AnnotationConfigServletWebServerApplicationContext();
            case REACTIVE:
                return new AnnotationConfigReactiveWebServerApplicationContext();
            default:
                return new AnnotationConfigApplicationContext();
        }
    }
    catch (Exception ex) {
        throw new IllegalStateException("Unable create a default ApplicationContext instance, "
                                        + "you may need a custom ApplicationContextFactory", ex);
    }
};           

是以:

  1. 應用類型為SERVLET,則對應AnnotationConfigServletWebServerApplicationContext
  2. 應用類型為REACTIVE,則對應AnnotationConfigReactiveWebServerApplicationContext
  3. 應用類型為普通類型,則對應AnnotationConfigApplicationContext

10、利用ApplicationContextInitializer初始化Spring容器對象

預設情況下SpringBoot提供了多個ApplicationContextInitializer,其中比較重要的有ConditionEvaluationReportLoggingListener,别看到它的名字叫XXXListener,但是它确實是實作了ApplicationContextInitializer接口的。

在它的initialize()方法中會:

  1. 将Spring容器指派給它的applicationContext屬性
  2. 并且往Spring容器中添加一個ConditionEvaluationReportListener(ConditionEvaluationReportLoggingListener的内部類),它是一個ApplicationListener
  3. 并生成一個ConditionEvaluationReport對象指派給它的report屬性

ConditionEvaluationReportListener會負責接收ContextRefreshedEvent事件,也就是Spring容器一旦啟動完畢就會觸發ContextRefreshedEvent,ConditionEvaluationReportListener就會列印自動配置類的條件評估報告。

11、觸發SpringApplicationRunListener的contextPrepared()

預設情況下會利用EventPublishingRunListener釋出一個ApplicationContextInitializedEvent事件,預設情況下暫時沒有ApplicationListener消費這個事件

12、調用DefaultBootstrapContext對象的close()

13、将啟動類作為配置類注冊到Spring容器中(load()方法)

将SpringApplication.run(MyApplication.class);中傳入進來的類,比如MyApplication.class,作為Spring容器的配置類

14、 觸發SpringApplicationRunListener的contextLoaded()

預設情況下會利用EventPublishingRunListener釋出一個ApplicationPreparedEvent事件

15、重新整理Spring容器

調用Spring容器的refresh()方法,結合第9、13步,相當于執行了這樣一個流程:

  1. AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
  2. applicationContext .register(MyApplication.class)
  3. applicationContext .refresh()

16、觸發SpringApplicationRunListener的started()

釋出ApplicationStartedEvent事件和AvailabilityChangeEvent事件,AvailabilityChangeEvent事件表示狀态變更狀态,變更後的狀态為LivenessState.CORRECT

LivenessState枚舉有兩個值:

  1. CORRECT:表示目前應用正常運作中
  2. BROKEN:表示目前應用還在運作,但是内部出現問題,暫時還沒發現哪裡用到了

17、調用ApplicationRunner和CommandLineRunner

  1. 擷取Spring容器中的ApplicationRunner類型的Bean
  2. 擷取Spring容器中的CommandLineRunner類型的Bean
  3. 執行它們的run()

18、觸發SpringApplicationRunListener的ready()

釋出ApplicationReadyEvent事件和AvailabilityChangeEvent事件,AvailabilityChangeEvent事件表示狀态變更狀态,變更後的狀态為ReadinessState.ACCEPTING_TRAFFIC

ReadinessState枚舉有兩個值:

  1. ACCEPTING_TRAFFIC:表示目前應用準備接收請求
  2. REFUSING_TRAFFIC:表示目前應用拒絕接收請求,比如Tomcat關閉時,就會釋出AvailabilityChangeEvent事件,并且狀态為REFUSING_TRAFFIC

19、上述過程抛異常了就觸發SpringApplicationRunListener的failed()

釋出ApplicationFailedEvent事件

配置檔案解析

流程圖:https://www.processon.com/view/link/62d399e71e08530a89222b23

SpringBoot完整的配置優先級

https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config

優先級由低到高(注意和我寫的順序是反的,我寫的是由高到底)

  1. Default properties (specified by setting SpringApplication.setDefaultProperties).
  2. @PropertySource annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.* which are read before refresh begins.
  3. Config data (such as application.properties files).
  4. A RandomValuePropertySource that has properties only in random.*.
  5. OS environment variables.
  6. Java System properties (System.getProperties()).
  7. JNDI attributes from java:comp/env. 不管它
  8. ServletContext init parameters.
  9. ServletConfig init parameters.
  10. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  11. Command line arguments.
  12. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  13. @TestPropertySource annotations on your tests.
  14. Devtools global settings properties in the $HOME/.config/spring-boot directory when devtools is active.