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

追風人聊Java發表於2021-12-26

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,歡迎大家關注

相關文章