從源碼角度解析 Springboot 2.6.2 的啟動過程

1. 概述

老話說的好:把簡單的事情重複做,做到極致,你就成功了。

 

言歸正傳,Springboot的啟動過程,一直都是面試的高頻點,今天我們用當前最新的 Springboot 2.6.2 來聊一聊 Springboot 的啟動過程。

 

2. 工程搭建

2.1 maven 依賴

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

 

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

 

2.2 application.yml 配置文件

server:
  port: 30000

spring:
  application:
    name: my-springboot

 

2.3 啟動類代碼

@SpringBootApplication
public class MySpringbootApplication {

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

 

3. Springboot 的啟動主流程

3.1 入口

入口當然就是我們 Springboot 啟動類中 main 方法里的這段代碼,SpringApplication.run 方法

 

3.2 初始化 SpringApplication 實例

3.2.1 方法總覽

我們進入 SpringApplication.run 這個靜態方法

 這裡調用了 另一個重載的 run 方法,再進

 

 此處會 new 一個 SpringApplication 對象,然後調用這個對象的 run 方法

 

 我們來看一下 SpringApplication 對象實例化時做的事情,這個構造方法調用了另一個重載的構造方法,我們進去看下

 

 

3.2.2 

this.resourceLoader = resourceLoader;  // resourceLoader 屬性注入了 null

 

3.2.3 

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));  // 將啟動類從數組重新封裝成了 Set,注入到 primarySources 屬性

 

3.2.4 

this.webApplicationType = WebApplicationType.deduceFromClasspath();  // 得到 web應用的類型,這裡是 SERVLET

webApplicationType  有三種類型,REACTIVE、SERVLET、NONE

引入 spring-boot-starter-web 包,就是 SERVLET

引入 spring-boot-starter-webflux 包,是 REACTIVE

都沒有就是 NONE

 

3.2.5 

this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

從 META-INF/spring.factories 文件中得到 key 為 org.springframework.boot.BootstrapRegistryInitializer 的全類名集合,進行實例化,然後注入 bootstrapRegistryInitializers 屬性

 

這裡大家先記下 getSpringFactoriesInstances 方法,等下詳細介紹

 

3.2.6

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

 

 

這一行代碼,只是封裝了一下,仍然還是調用 getSpringFactoriesInstances 方法,從 META-INF/spring.factories 文件中得到 key 為

org.springframework.context.ApplicationContextInitializer 的全類名集合,進行實例化,然後注入 initializers(初始化器集合) 屬性。

 

3.2.7 

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));  // 同理,得到監聽器實例的集合,並注入

 

3.2.8

this.mainApplicationClass = deduceMainApplicationClass();  // 獲取當前運行的 main 方法所在的類,也就是咱們的主類

 

3.3 執行 run 方法

3.3.1 方法總覽

 我們回到這個方法體,進入 run 方法

 

 

 方法有點長。。。,沒關係,我們撿重點看看

 

3.3.2 

long startTime = System.nanoTime();  // 記錄一個開始時間戳

 

3.3.3 

DefaultBootstrapContext bootstrapContext = createBootstrapContext();  // 添加了一個默認的 Bootstrap 上下文

 從代碼看,就是 new 了一個 DefaultBootstrapContext 實例,然後遍歷初始化了 bootstrapRegistryInitializers 中的所有初始化器

還記得 bootstrapRegistryInitializers 屬性嗎,3.2.5 章節中,實例化 SpringApplication 時通過  getSpringFactoriesInstances 方法獲得並注入的。

 

3.3.4

configureHeadlessProperty();  // 配置Headless屬性

 

3.3.5

SpringApplicationRunListeners listeners = getRunListeners(args);  // 獲得 RunListener 集合類

 

這裡我們又看到了熟悉的 getSpringFactoriesInstances,這次的 key 是 org.springframework.boot.SpringApplicationRunListener

這裡會得到 EventPublishingRunListener 對象

 

3.3.6

listeners.starting(bootstrapContext, this.mainApplicationClass);  // 循環啟動這些監聽器

 

從代碼看,會調用監聽器的 starting 方法,我們看一下 EventPublishingRunListener 對象的 starting 方法

 

 

 

 

 

從代碼看,在 EventPublishingRunListener 對象的 starting 方法中,做了一個廣播,得到應用監聽器後,循環調用監聽器的 onApplicationEvent 方法

 

3.3.7 

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);  //  封裝參數

 

3.3.8 

ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);  // 創建並配置環境

 

首先會創建環境,因為 webApplicationType 是 SERVLET,因此會創建 ApplicationServletEnvironment 對象

 

listeners.environmentPrepared(bootstrapContext, environment);

重點是這句

 listeners.environmentPrepared 方法會執行 EventPublishingRunListener 對象的 environmentPrepared 方法

 

 來到 EventPublishingRunListener 對象的方法,同樣是一個廣播,廣播給合適的監聽器,然後調用監聽器的 onApplicationEvent 方法

 

其中在 EnvironmentPostProcessorApplicationListener 監聽器中,會執行拿到所有系統的配置,包括我們在 application.yml 文件中配置的內容。 

我們來看一下 EnvironmentPostProcessorApplicationListener  這個類

 

 

 在 EnvironmentPostProcessorApplicationListener  中,會得到環境的處理器,然後循環執行他們

 

 這裡可以得到 7 個處理器,其中 ConfigDataEnvironmentPostProcessor 就是加載配置文件得到配置的,我們來看一下這個類的 postProcessEnvironment 方法

 

 在方法中,執行 processAndApply() 方法,最終拿到配置

 

 當 listeners.environmentPrepared(bootstrapContext, environment); 最終執行完,我們從 environment 對象中就可以找到我們在 yml 文件中配置的 端口 和 應用名稱

 

3.3.9 

Banner printedBanner = printBanner(environment);  // 打印 Banner

 

3.3.10

context = createApplicationContext();  // 實例化上下文對象

 因為類型是 SERVLET,所以實例化的是 AnnotationConfigServletWebServerApplicationContext 對象

 

3.3.11 

prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);  // 準備上下文

 

 

 

3.3.12

refreshContext(context);  // 刷新上下文

主要邏輯在 AbstractApplicationContext 對象的 refresh 方法中

 

 

 

 

3.3.13

afterRefresh(context, applicationArguments);  // 空方法

 

3.3.14 

Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() – startTime);  // 計算耗時

 

3.3.15

listeners.started(context, timeTakenToStartup);  // 監聽器執行  started 方法,表示啟動成功

 

3.3.16

callRunners(context, applicationArguments);  // 回調所有的ApplicationRunner和CommandLineRunner

 

3.3.17

listeners.ready(context, timeTakenToReady);  // 監聽器執行 ready 方法

 

4. 流程總結

1)實例化 SpringApplication 對象 

2)得到 初始化器 和 監聽器

3)調用 run 方法

4)記錄開始時間

5)得到 runListeners

6)runListeners 執行 starting

7)準備環境

8)打印 banner

9)實例化上下文對象

10)準備上下文,執行之前得到的初始化器的初始化方法,load主bean

11)刷新上下文,在其中加載 autoConfiguration,並啟動 Tomcat

12)計算耗時

13)打印耗時

14)通知監聽器啟動完成

15)通知監聽器 ready

 

5. getSpringFactoriesInstances 方法詳解

 

 這裏面比較關鍵的邏輯是 得到類的全類名集合 和 實例化類

 

 

 

 

 從這些代碼我們可以得知,會從 META-INF/spring.factories 文件中找到 key 匹配的類,並把類的全路徑集合得到

 

例如實例化 SpringApplication 對象時,獲得 初始化器 和 監聽器

 

 之後通過全類名,使用反射技術,實例化類,最終得到想要的集合

 

6. 綜述

今天聊了一下 Springboot 2.6.2 的啟動過程,希望可以對大家的工作有所幫助

歡迎幫忙點贊、評論、轉發、加關注 :)

關注追風人聊Java,每天更新Java乾貨。

 

7. 個人公眾號

追風人聊Java,歡迎大家關注