SpringBoot 啟動過程筆記

本文主要是記錄自己學習的過程,主要參考了下面所附三篇文檔的內容,總結了一下。

1 概述

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(AppServer.class);
        application.run(args);
    }
}

Spring的啟動過程可以分成三個部分:
第一部分進行SpringApplication的初始化模塊
配置一些基本的環境變量、資源、構造器、監聽器。
第二部分實現了應用具體的啟動方案
包括啟動流程的監聽模塊、加載配置環境模塊、及核心的創建上下文環境模塊
第三部分是自動化配置模塊
該模塊作為springboot自動配置核心

Copy的別人的一個啟動結構圖:


image.png

2 初始化模塊

SpringApplication的構造函數實例化了 初始化上下文的各種接口--ApplicationContextInitializer以及監聽器--ApplicationListener。
這里的實例化,不像平時的Spring Components一樣通過注解和掃包完成,而是通過一種不依賴Spring上下文的加載方法,這樣才能在Spring完成啟動前做各種配置。
Spring的解決方法是以接口的全限定名作為key,實現類的全限定名作為value記錄在項目的META-INF/spring.factories文件中,然后通過SpringFactoriesLoader工具類提供靜態方法進行類加載并緩存下來,spring.factories是Spring Boot的核心配置文件。
有意思的是兩個deduce方法,Spring Boot項目主要的目標之一就是自動化配置,通過這兩個deduce方法可以看出,Spring Boot的判斷方法之一是檢查系統中是否存在的核心類。

  1. 判斷了當前類型是否web
  2. 加載所有的初始化器
  3. 加載所有的監聽器
  4. 設置程序運行的主類
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //通過核心類判斷是否開啟、開啟什么web容器
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //實例化初始器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //實例化監聽器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   //設置程序運行的主類
    this.mainApplicationClass = deduceMainApplicationClass();
}

3 啟動流程詳解

Springboot啟動的流程,可以將其劃分為18步。具體如下:

1.創建并啟動計時監控類
org.springframework.util下的StopWatch類
此計時器是為了監控并記錄 Spring Boot 應用啟動的時間的,它會記錄當前任務的名稱,然后開啟計時器。

2.聲明應用上下文對象和異常報告集合

此過程聲明了應用上下文對象。
同時聲明了異常報告集合用于收集錯誤信息,用于向用戶報告錯誤原因。

3.設置系統屬性 headless 的值
4.創建所有 Spring 運行監聽器并發布應用啟動事件

此過程用于獲取配置的監聽器名稱并實例化所有的類。

5.初始化默認應用的參數類

也就是說聲明并創建一個應用參數對象。

6.準備環境

創建配置并且綁定環境(通過 property sources 和 profiles 等配置文件)。

7.創建 Banner 的打印類

Spring Boot 啟動時會打印 Banner 圖片

此 banner 信息是在 SpringBootBanner 類中定義的,我們可以通過實現 Banner 接口來自定義 banner 信息,然后通過代碼 setBanner() 方法設置 Spring Boot 項目使用自己自定義 Banner 信息,或者是在 resources 下添加一個 banner.txt,把 banner 信息添加到此文件中,就可以實現自定義 banner 的功能了。

8.創建應用上下文

根據不同的應用類型來創建不同的 ApplicationContext 上下文對象。

9.實例化異常報告器

它調用的是 getSpringFactoriesInstances() 方法來獲取配置異常類的名稱,并實例化所有的異常處理類。

10.準備應用上下文

此方法的主要作用是把上面已經創建好的對象,傳遞給 prepareContext 來準備上下文,例如將環境變量 environment 對象綁定到上下文中、配置 bean 生成器以及資源加載器、記錄啟動日志等操作。

11.刷新應用上下文

此方法用于解析配置文件,加載 bean 對象,并且啟動內置的 web 容器等操作。

12.應用上下文刷新之后的事件處理

13.停止計時監控類

停止此過程第一步中的程序計時器,并統計任務的執行信息。

14.輸出日志信息

把相關的記錄信息,如類名、時間等信息進行控制臺輸出。

15.發布應用上下文啟動完成事件

觸發所有 SpringApplicationRunListener 監聽器的 started 事件方法。

16.執行所有 Runner 運行器

執行所有的 ApplicationRunner 和 CommandLineRunner 運行器。

17.發布應用上下文就緒事件

觸發所有的 SpringApplicationRunListener 監聽器的 running 事件。

18.返回應用上下文對象

public ConfigurableApplicationContext run(String... args){
   // 1. 創建并啟動計時監控類
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // 2. 聲明應用上下文對象和異常報告集合
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    // 3. 設置系統屬性 headless 的值
    this.configureHeadlessProperty();
    // 4. 創建所有 Spring 運行監聽器并發布應用啟動事件
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();
    Collection exceptionReporters;
    try {
        // 5. 處理 args 參數
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 6. 準備環境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        //7. 創建 Banner 的打印類
        Banner printedBanner = this.printBanner(environment);
        // 8. 創建應用上下文
        context = this.createApplicationContext();
        // 9. 實例化異常報告器
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        // 10. 準備應用上下文
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 11. 刷新應用上下文
        this.refreshContext(context);
        // 12. 應用上下文刷新之后的事件的處理
        this.afterRefresh(context, applicationArguments);
        // 13. 停止計時監控類
        stopWatch.stop();
        // 14. 輸出日志記錄執行主類名、時間信息
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }
        // 15. 發布應用上下文啟動完成事件
        listeners.started(context);
        // 16. 執行所有 Runner 運行器
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }
    try {
        // 17. 發布應用上下文就緒事件
        listeners.running(context);
        // 18. 返回應用上下文對象
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}

4 自動化配置

第11步的刷新應用上下文<refreshContext(context)>方法是實現spring-boot-starter-*(mybatis、redis等)自動化配置的關鍵,包括spring.factories的加載,bean的實例化等核心工作。

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        //記錄啟動時間、狀態,web容器初始化其property,復制listener
        prepareRefresh();
        //這里返回的是context的BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        //beanFactory注入一些標準組件,例如ApplicationContextAwareProcessor,ClassLoader等
        prepareBeanFactory(beanFactory);
        try {
            //給實現類留的一個鉤子,例如注入BeanPostProcessors,這里是個空方法
            postProcessBeanFactory(beanFactory);

            // 調用切面方法
            invokeBeanFactoryPostProcessors(beanFactory);

            // 注冊切面bean
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            initMessageSource();

            // bean工廠注冊一個key為applicationEventMulticaster的廣播器
            initApplicationEventMulticaster();

            // 給實現類留的一鉤子,可以執行其他refresh的工作,這里是個空方法
            onRefresh();

            // 將listener注冊到廣播器中
            registerListeners();

            // 實例化未實例化的bean
            finishBeanFactoryInitialization(beanFactory);

            // 清理緩存,注入DefaultLifecycleProcessor,發布ContextRefreshedEvent
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

從上面的應用初始化和啟動過程中可以看到,都調用了SpringBoot自動配置模塊.

image.png

上面配置模塊使用到了SpringFactoriesLoader,即Spring工廠加載器,該對象提供了loadFactoryNames方法,入參為factoryClass和classLoader,即需要傳入上圖中的工廠類名稱和對應的類加載器,方法會根據指定的classLoader,加載該類加器搜索路徑下的指定文件,即spring.factories文件,傳入的工廠類為接口,而文件中對應的類則是接口的實現類,或最終作為實現類,所以文件中一般為如下圖這種一對多的類名集合,獲取到這些實現類的類名后,loadFactoryNames方法返回類名集合,方法調用方得到這些集合后,再通過反射獲取這些類的類對象、構造方法,最終生成實例。

image.png

參考:
Spring Boot啟動過程分析
Creating a Custom Starter with Spring Boot
SpringBoot啟動流程解析

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,639評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內容