天天看點

SpringBoot的啟動流程是怎樣的?SpringBoot源碼(七)1 溫故而知新2 引言3 如何編寫一個SpringBoot啟動類4 @SpringBootApplication5 SpringBoot的啟動流程是怎樣的?6 小結

注:該源碼分析對應SpringBoot版本為2.1.0.RELEASE

1 溫故而知新

本篇接

SpringBoot内置的各種Starter是怎樣建構的? SpringBoot源碼(六)

溫故而知新,我們來簡單回顧一下上篇的内容,上一篇我們分析了SpringBootSpringBoot内置的各種Starter是怎樣建構的?,現将關鍵點重新回顧總結下:

  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

的自動配置的源碼。

是以,本篇我們來探究下SpringBoot的啟動流程是怎樣的?

3 如何編寫一個SpringBoot啟動類

我們都知道,我們運作一個SpringBoot項目,引入相關

Starters

和相關依賴後,再編寫一個啟動類,然後在這個啟動類标上

@SpringBootApplication

注解,然後就可以啟動運作項目了,如下代碼:

//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的啟動流程,非常簡單。

4 @SpringBootApplication

現在我們來分析下标注在啟動類上的

@SpringBootApplication

注解,直接上源碼:

// 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容器。

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. 調用

    ApplicationRunner

    CommandLineRunner

    的run方法,我們實作這兩個接口可以在spring容器啟動後需要的一些東西比如加載一些業務資料等;
  10. 報告啟動異常,即若啟動過程中抛出異常,此時用

    FailureAnalyzers

    來報告異常;
  11. 最終傳回容器對象,這裡調用方法沒有聲明對象來接收。

當然在SpringBoot啟動過程中,每個不同的啟動階段會分别發射不同的内置生命周期事件,比如在準備

environment

前會發射

ApplicationStartingEvent

事件,在

environment

準備好後會發射

ApplicationEnvironmentPreparedEvent

事件,在重新整理容器前會發射

ApplicationPreparedEvent

事件等,總之SpringBoot總共内置了7個生命周期事件,除了标志SpringBoot的不同啟動階段外,同時一些監聽器也會監聽相應的生命周期事件進而執行一些啟動初始化邏輯。

6 小結

好了,SpringBoot的啟動流程就已經分析完了,這篇内容主要讓我們對SpringBoot的啟動流程有一個整體的認識,現在還沒必要去深究每一個細節,以免丢了主線,現在我們對SpringBoot的啟動流程有一個整體的認識即可,關于啟動流程的一些重要步驟我們會在以後的源碼分析中來深究。

原創不易,幫忙點個贊呗!

由于筆者水準有限,若文中有錯誤還請指出,謝謝。

歡迎關注【源碼筆記】公衆号,一起學習交流。