SpringBoot的啓動流程是怎樣的?SpringBoot源碼(七)

注:該源碼分析對應SpringBoot版本爲2.1.0.RELEASEjava

1 溫故而知新

本篇接 SpringBoot內置的各類Starter是怎樣構建的? SpringBoot源碼(六)web

溫故而知新,咱們來簡單回顧一下上篇的內容,上一篇咱們分析了SpringBootSpringBoot內置的各類Starter是怎樣構建的?,現將關鍵點從新回顧總結下:spring

  1. spring-boot-starter-xxx起步依賴沒有一行代碼,而是直接或間接依賴了xxx-autoconfigure模塊,而xxx-autoconfigure模塊承擔了spring-boot-starter-xxx起步依賴自動配置的實現;
  2. xxx-autoconfigure自動配置模塊引入了一些可選依賴,這些可選依賴不會被傳遞到spring-boot-starter-xxx起步依賴中,這是起步依賴構建的關鍵點
  3. spring-boot-starter-xxx起步依賴顯式引入了一些對自動配置起做用的可選依賴,所以會觸發 xxx-autoconfigure自動配置的邏輯(好比建立某些符合條件的配置bean);
  4. 通過前面3步的準備,咱們項目只要引入了某個起步依賴後,就能夠開箱即用了,而不用手動去建立一些bean等。

2 引言

原本這篇文章會繼續SpringBoot自動配置的源碼分析的,想分析下spring-boot-starter-web的自動配置的源碼是怎樣的的。可是考慮到spring-boot-starter-web的自動配置邏輯跟內置Tomcat等有關,所以想之後等分析了SpringBoot的內置Tomcat的相關源碼後再來繼續分析spring-boot-starter-web的自動配置的源碼。tomcat

所以,本篇咱們來探究下SpringBoot的啓動流程是怎樣的?服務器

3 如何編寫一個SpringBoot啓動類

咱們都知道,咱們運行一個SpringBoot項目,引入相關Starters和相關依賴後,再編寫一個啓動類,而後在這個啓動類標上@SpringBootApplication註解,而後就能夠啓動運行項目了,以下代碼:app

//MainApplication.java

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

如上代碼,咱們在MainApplication啓動類上標註了@SpringBootApplication註解,而後在main函數中調用SpringApplication.run(MainApplication.class, args);這句代碼就完成了SpringBoot的啓動流程,很是簡單。less

4 @SpringBootApplication

如今咱們來分析下標註在啓動類上的@SpringBootApplication註解,直接上源碼:ide

// SpringBootApplication.java 

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { // TODO 這兩個排除過濾器TypeExcludeFilter和AutoConfigurationExcludeFilter暫不知道啥做用
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
        // 等同於EnableAutoConfiguration註解的exclude屬性
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
        // 等同於EnableAutoConfiguration註解的excludeName屬性
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
        // 等同於ComponentScan註解的basePackages屬性
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
        // 等同於ComponentScan註解的basePackageClasses屬性
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};
}

能夠看到,@SpringBootApplication註解是一個組合註解,主要由@SpringBootConfiguration,@EnableAutoConfiguration@ComponentScan這三個註解組合而成。函數

所以@SpringBootApplication註解主要做爲一個配置類,可以觸發包掃描和自動配置的邏輯,從而使得SpringBoot的相關bean被註冊進Spring容器。spring-boot

5 SpringBoot的啓動流程是怎樣的?

接下來是本篇的重點,咱們來分析下SpringBoot的啓動流程是怎樣的?

咱們接着來看前面main函數裏的SpringApplication.run(MainApplication.class, args);這句代碼,那麼SpringApplication這個類是幹嗎的呢?

SpringApplication類是用來啓動SpringBoot項目的,能夠在java的main方法中啓動,目前咱們知道這些就足夠了。下面看下SpringApplication.run(MainApplication.class, args);這句代碼的源碼:

// SpringApplication.java

// run方法是一個靜態方法,用於啓動SpringBoot
public static ConfigurableApplicationContext run(Class<?> primarySource,
        String... args) {
    // 繼續調用靜態的run方法
    return run(new Class<?>[] { primarySource }, args);
}

在上面的靜態run方法裏又繼續調用另外一個靜態run方法:

// SpringApplication.java

// run方法是一個靜態方法,用於啓動SpringBoot
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
        String[] args) {
    // 構建一個SpringApplication對象,並調用其run方法來啓動
    return new SpringApplication(primarySources).run(args);
}

如上代碼,能夠看到構建了一個SpringApplication對象,而後再調用其run方法來啓動SpringBoot項目。關於SpringApplication對象是如何構建的,咱們後面再分析,如今直接來看下啓動流程的源碼:

// SpringApplication.java

public ConfigurableApplicationContext run(String... args) {
    // new 一個StopWatch用於統計run啓動過程花了多少時間
    StopWatch stopWatch = new StopWatch();
    // 開始計時
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    // exceptionReporters集合用來存儲異常報告器,用來報告SpringBoot啓動過程的異常
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 配置headless屬性,即「java.awt.headless」屬性,默認爲ture
    // 實際上是想設置該應用程序,即便沒有檢測到顯示器,也容許其啓動.對於服務器來講,是不須要顯示器的,因此要這樣設置.
    configureHeadlessProperty();
    // 【1】從spring.factories配置文件中加載到EventPublishingRunListener對象並賦值給SpringApplicationRunListeners
    // EventPublishingRunListener對象主要用來發射SpringBoot啓動過程當中內置的一些生命週期事件,標誌每一個不一樣啓動階段
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 啓動SpringApplicationRunListener的監聽,表示SpringApplication開始啓動。
    // 》》》》》發射【ApplicationStartingEvent】事件
    listeners.starting();
    try {
        // 建立ApplicationArguments對象,封裝了args參數
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        // 【2】準備環境變量,包括系統變量,環境變量,命令行參數,默認變量,servlet相關配置變量,隨機值,
        // JNDI屬性值,以及配置文件(好比application.properties)等,注意這些環境變量是有優先級的
        // 》》》》》發射【ApplicationEnvironmentPreparedEvent】事件
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        // 配置spring.beaninfo.ignore屬性,默認爲true,即跳過搜索BeanInfo classes.
        configureIgnoreBeanInfo(environment);
        // 【3】控制檯打印SpringBoot的bannner標誌
        Banner printedBanner = printBanner(environment);
        // 【4】根據不一樣類型建立不一樣類型的spring applicationcontext容器
        // 由於這裏是servlet環境,因此建立的是AnnotationConfigServletWebServerApplicationContext容器對象
        context = createApplicationContext();
        // 【5】從spring.factories配置文件中加載異常報告期實例,這裏加載的是FailureAnalyzers
        // 注意FailureAnalyzers的構造器要傳入ConfigurableApplicationContext,由於要從context中獲取beanFactory和environment
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context); // ConfigurableApplicationContext是AnnotationConfigServletWebServerApplicationContext的父接口
        // 【6】爲剛建立的AnnotationConfigServletWebServerApplicationContext容器對象作一些初始化工做,準備一些容器屬性值等
        // 1)爲AnnotationConfigServletWebServerApplicationContext的屬性AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner設置environgment屬性
        // 2)根據狀況對ApplicationContext應用一些相關的後置處理,好比設置resourceLoader屬性等
        // 3)在容器刷新前調用各個ApplicationContextInitializer的初始化方法,ApplicationContextInitializer是在構建SpringApplication對象時從spring.factories中加載的
        // 4)》》》》》發射【ApplicationContextInitializedEvent】事件,標誌context容器被建立且已準備好
        // 5)從context容器中獲取beanFactory,並向beanFactory中註冊一些單例bean,好比applicationArguments,printedBanner
        // 6)TODO 加載bean到application context,注意這裏只是加載了部分bean好比mainApplication這個bean,大部分bean應該是在AbstractApplicationContext.refresh方法中被加載?這裏留個疑問先
        // 7)》》》》》發射【ApplicationPreparedEvent】事件,標誌Context容器已經準備完成
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        // 【7】刷新容器,這一步相當重要,之後會在分析Spring源碼時詳細分析,主要作了如下工做:
        // 1)在context刷新前作一些準備工做,好比初始化一些屬性設置,屬性合法性校驗和保存容器中的一些早期事件等;
        // 2)讓子類刷新其內部bean factory,注意SpringBoot和Spring啓動的狀況執行邏輯不同
        // 3)對bean factory進行配置,好比配置bean factory的類加載器,後置處理器等
        // 4)完成bean factory的準備工做後,此時執行一些後置處理邏輯,子類經過重寫這個方法來在BeanFactory建立並預準備完成之後作進一步的設置
        // 在這一步,全部的bean definitions將會被加載,但此時bean還不會被實例化
        // 5)執行BeanFactoryPostProcessor的方法即調用bean factory的後置處理器:
        // BeanDefinitionRegistryPostProcessor(觸發時機:bean定義註冊以前)和BeanFactoryPostProcessor(觸發時機:bean定義註冊以後bean實例化以前)
        // 6)註冊bean的後置處理器BeanPostProcessor,注意不一樣接口類型的BeanPostProcessor;在Bean建立先後的執行時機是不同的
        // 7)初始化國際化MessageSource相關的組件,好比消息綁定,消息解析等
        // 8)初始化事件廣播器,若是bean factory沒有包含事件廣播器,那麼new一個SimpleApplicationEventMulticaster廣播器對象並註冊到bean factory中
        // 9)AbstractApplicationContext定義了一個模板方法onRefresh,留給子類覆寫,好比ServletWebServerApplicationContext覆寫了該方法來建立內嵌的tomcat容器
        // 10)註冊實現了ApplicationListener接口的監聽器,以前已經有了事件廣播器,此時就能夠派發一些early application events
        // 11)完成容器bean factory的初始化,並初始化全部剩餘的單例bean。這一步很是重要,一些bean postprocessor會在這裏調用。
        // 12)完成容器的刷新工做,而且調用生命週期處理器的onRefresh()方法,而且發佈ContextRefreshedEvent事件
        refreshContext(context);
        // 【8】執行刷新容器後的後置處理邏輯,注意這裏爲空方法
        afterRefresh(context, applicationArguments);
        // 中止stopWatch計時
        stopWatch.stop();
        // 打印日誌
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        // 》》》》》發射【ApplicationStartedEvent】事件,標誌spring容器已經刷新,此時全部的bean實例都已經加載完畢
        listeners.started(context);
        // 【9】調用ApplicationRunner和CommandLineRunner的run方法,實現spring容器啓動後須要作的一些東西好比加載一些業務數據等
        callRunners(context, applicationArguments);
    }
    // 【10】若啓動過程當中拋出異常,此時用FailureAnalyzers來報告異常
    // 並》》》》》發射【ApplicationFailedEvent】事件,標誌SpringBoot啓動失敗
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 》》》》》發射【ApplicationReadyEvent】事件,標誌SpringApplication已經正在運行即已經成功啓動,能夠接收服務請求了。
        listeners.running(context);
    }
    // 若出現異常,此時僅僅報告異常,而不會發射任何事件
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    // 【11】最終返回容器
    return context;
}

如上代碼就是SpringBoot的啓動流程了,其中註釋也很是詳細,主要步驟也已經標註【x】,現將主要步驟總結以下:

  1. spring.factories配置文件中加載EventPublishingRunListener對象,該對象擁有SimpleApplicationEventMulticaster屬性,即在SpringBoot啓動過程的不一樣階段用來發射內置的生命週期事件;
  2. 準備環境變量,包括系統變量,環境變量,命令行參數,默認變量,servlet相關配置變量,隨機值以及配置文件(好比application.properties)等;
  3. 控制檯打印SpringBoot的bannner標誌
  4. 根據不一樣類型環境建立不一樣類型的applicationcontext容器,由於這裏是servlet環境,因此建立的是AnnotationConfigServletWebServerApplicationContext容器對象;
  5. spring.factories配置文件中加載FailureAnalyzers對象,用來報告SpringBoot啓動過程當中的異常;
  6. 爲剛建立的容器對象作一些初始化工做,準備一些容器屬性值等,對ApplicationContext應用一些相關的後置處理和調用各個ApplicationContextInitializer的初始化方法來執行一些初始化邏輯等;
  7. 刷新容器,這一步相當重要。好比調用bean factory的後置處理器,註冊BeanPostProcessor後置處理器,初始化事件廣播器且廣播事件,初始化剩下的單例bean和SpringBoot建立內嵌的Tomcat服務器等等重要且複雜的邏輯都在這裏實現,主要步驟可見代碼的註釋,關於這裏的邏輯會在之後的spring源碼分析專題詳細分析;
  8. 執行刷新容器後的後置處理邏輯,注意這裏爲空方法;
  9. 調用ApplicationRunnerCommandLineRunner的run方法,咱們實現這兩個接口能夠在spring容器啓動後須要的一些東西好比加載一些業務數據等;
  10. 報告啓動異常,即若啓動過程當中拋出異常,此時用FailureAnalyzers來報告異常;
  11. 最終返回容器對象,這裏調用方法沒有聲明對象來接收。

固然在SpringBoot啓動過程當中,每一個不一樣的啓動階段會分別發射不一樣的內置生命週期事件,好比在準備environment前會發射ApplicationStartingEvent事件,在environment準備好後會發射ApplicationEnvironmentPreparedEvent事件,在刷新容器前會發射ApplicationPreparedEvent事件等,總之SpringBoot總共內置了7個生命週期事件,除了標誌SpringBoot的不一樣啓動階段外,同時一些監聽器也會監聽相應的生命週期事件從而執行一些啓動初始化邏輯。

6 小結

好了,SpringBoot的啓動流程就已經分析完了,這篇內容主要讓咱們對SpringBoot的啓動流程有一個總體的認識,如今還不必去深究每個細節,以避免丟了主線,如今咱們對SpringBoot的啓動流程有一個總體的認識便可,關於啓動流程的一些重要步驟咱們會在之後的源碼分析中來深究。

原創不易,幫忙點個讚唄!

因爲筆者水平有限,若文中有錯誤還請指出,謝謝。


歡迎關注【源碼筆記】公衆號,一塊兒學習交流。
SpringBoot的啓動流程是怎樣的?SpringBoot源碼(七)

相關文章
相關標籤/搜索