天天看點

三分鐘了解 SpringBoot 的啟動流程

作者:老誠不bug

0.前言

背景:最近有位開發同學說面試被問到Spring Boot 的啟動流程,以及被問到Spring Boot 的嵌入式Web容器是什麼時候加載的。如何加載的。是怎麼無縫切換的。

這些問題,其實回答起來也是比較複雜的。我們今天就從 SpringApplication.run(EasyPaasAdminApplication.class, args);入口,逐漸向下看下執行流程,來試着回答一下前面這兩個問題。後面關于SpringBoot 的web容器可以無縫随意切換為jetty,undertow..這個問題的回答涉及到Spring Boot是如何設計WebServer的。我們後續專門講解一下。

1. 執行邏輯梳理

一般我們SpringBoot 應用的啟動入口都是如下這種固定的寫法,

三分鐘了解 SpringBoot 的啟動流程

也可以是這樣

public static void main(String[] args) {
   SpringApplication application = new SpringApplication(MyApplication.class);
   // ... customize application settings here
   application.run(args)
  }
           

但總之,都是使用SpringApplication 調用靜态方法

此方法的注釋

Static helper that can be used to run a SpringApplication from the specified source using default settings.
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
  return run(new Class<?>[] { primarySource }, args);
 }
           

跟過來就到這,可以看到注釋運作Spring應用程式,建立并重新整理一個新的ApplicationContext。

三分鐘了解 SpringBoot 的啟動流程
三分鐘了解 SpringBoot 的啟動流程

跟代碼到這兒其實我們對于SpringBoot 的基本啟動流程已經知道了。但是要解答什麼時候啟動的Tomcat 還需要繼續分析。

三分鐘了解 SpringBoot 的啟動流程

到這兒我們就可以繼續下去,發現Spring Boot 啟動WebServer。此處的WebServer我就不展開了,可以點選去就三個方法start ,stop,getPort。

可以看出來Spring 在設計接口的時候還是很嚴謹和精簡。我們的核心脈絡是梳理SpringBoot 啟動過程,并且回答Tomcat 是如何被啟動的。

三分鐘了解 SpringBoot 的啟動流程

我們可以看到WebServer 的實作目前内置的有5種。其實Spring Boot 還有一個特性叫做 自動裝配。

這就是為什麼5個實作,我們最後啟動的是Tomcat。此處也不做展開。後面我專門搞一個解析SpringBoot 自動裝配的文章。

三分鐘了解 SpringBoot 的啟動流程

我們看一下内部start 的TomcatWebServer的内部實作。了解過Tomcat 源碼的同學看到這兒就基本明白了。

三分鐘了解 SpringBoot 的啟動流程

好源碼跟進過程我們到此結束,我們整理和總結一下。

通過掃一遍源碼我們大概可以總結出來如下三個階段

準備階段、應用上下文建立階段、重新整理上下文階段。
  1. 準備階段: Spring Boot 會加載應用程式的初始設定,并建立 Spring Boot 上下文。這個階段的核心源碼是 SpringApplication 類的 run() 方法,它會調用 Spring Boot 的各個初始化器進行初始化和準備工作。
  2. 應用上下文建立階段 : Spring Boot 會建立應用程式的上下文,包括各種配置資訊、Bean 的加載和初始化等。這個階段的核心源碼是 Spring Boot 自動配置機制,通過掃描 classpath 中的配置檔案,自動加載和配置各種元件和 Bean。
  3. 重新整理上下文階段: Spring Boot 會執行各種啟動任務,包括建立 Web 伺服器、加載應用程式的配置、初始化各種元件等。這個階段的核心源碼是 Spring Boot 的重新整理機制,它會調用各種初始化器和監聽器,執行各種啟動任務。其中啟動Tomcat 就是在這個環節進行。

2. 核心源碼解析

既然上面我們已經基本上總結除了,Spring Boot的啟動脈絡。也梳理出了一些核心源碼。那麼我們對啟動過程的核心源碼解析一下。

2.1. 準備階段

在準備階段中,Spring Boot 會加載應用程式的初始設定,并建立 Spring Boot 上下文。這個階段的核心源碼是 SpringApplication 類的 run() 方法,它會調用 Spring Boot 的各個初始化器進行初始化和準備工作。

public ConfigurableApplicationContext run(String... args) {
                 // 啟動計時器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

                 // 定義應用程式上下文和異常報告器清單
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

                 // 配置 Headless 屬性
        configureHeadlessProperty();

                 // 擷取 Spring Boot 啟動監聽器
        SpringApplicationRunListeners listeners = getRunListeners(args);
                 // 執行啟動監聽器的 starting 方法
        listeners.starting();

        try {
                 // 解析指令行參數
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                 // 準備應用程式環境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
                 // 配置忽略 BeanInfo
            configureIgnoreBeanInfo(environment);
                 // 列印 Banner
            Banner printedBanner = printBanner(environment);
                 // 建立應用程式上下文
            context = createApplicationContext();
                 // 擷取異常報告器,關于異常報告,我下次專門講一下SpringBoot 的異常收集器。
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
                 // 準備應用程式上下文
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                 // 重新整理應用程式上下文
            refreshContext(context);
                 // 重新整理後操作
            afterRefresh(context, applicationArguments);
                 // 停止計時器
            stopWatch.stop();
                 // 記錄啟動日志
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
                 // 執行啟動監聽器的 started 方法
            listeners.started(context);
                 // 執行 Runner
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
                 // 處理啟動失敗
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
                 // 執行啟動監聽器的 running 方法
            listeners.running(context);
        } catch (Throwable ex) {
                 // 處理啟動失敗
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }

                 // 傳回應用程式上下文
        return context;
    }
           

在 run() 方法中,Spring Boot 首先會建立一個 StopWatch 對象,用于記錄整個啟動過程的耗時。然後,Spring Boot 會調用 getRunListeners(args) 方法擷取 Spring Boot 的各個啟動監聽器,并調用starting() 方法通知這些監聽器啟動過程已經開始。接着調用 prepareEnvironment(listeners, applicationArguments) 方法建立應用程式的環境變量。

這個方法會根據使用者的配置和預設設定建立一個 ConfigurableEnvironment對象,并将其傳給後面的 createApplicationContext() 方法。printBanner(environment) 方法列印啟動界面的 Banner,調用 refreshContext(context)方法重新整理上下文。

這個方法會啟動上下文,執行各種啟動任務,包括建立 Web 伺服器、加載應用程式的配置、初始化各種元件等。具體的啟動任務會在重新整理上下文階段中進行。

2.2. 應用上下文建立階段

在應用上下文建立階段中,Spring Boot 會建立應用程式的上下文,包括各種配置資訊、Bean 的加載和初始化等。這個階段的核心源碼是 Spring Boot 自動配置機制,通過掃描 classpath 中的配置檔案,自動加載和配置各種元件和 Bean。

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable to create a default ApplicationContext, " +
                    "please specify an ApplicationContextClass", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
           

在 createApplicationContext() 方法中,Spring Boot 首先會判斷應用程式的類型,如果是 Web 應用程式,則會建立一個 WebApplicationContext;否則,會建立一個普通的 ApplicationContext。調用 BeanUtils.instantiateClass(contextClass) 方法建立應用程式的上下文。這個方法會根據上面的邏輯建立一個相應的 ApplicationContext。調用 load() 方法加載應用程式的配置。

關于加載應用配置,可以參閱我之前寫一篇文章《三分鐘了解SpringBoot配置優先級底層源碼解析》。這個方法會掃描 classpath 中的各種配置檔案,例如 application.properties、application.yml、META-INF/spring.factories 等,自動配置各種元件和 Bean。調用 postProcessApplicationContext() 方法對應用程式的上下文進行後處理。這個方法會調用各種初始化器和監聽器,執行各種初始化任務。

https://blog.csdn.net/wangshuai6707/article/details/130966177

2.3. 重新整理上下文階段

在重新整理上下文階段中,Spring Boot 會執行各種啟動任務,包括建立 Web 伺服器(剛才我們跟源碼的時候也看到了,如上我的截圖)、加載應用程式的配置、初始化各種元件等。這個階段的核心源碼是 Spring Boot 的重新整理機制,它會調用各種初始化器和監聽器,執行各種啟動任務。

protected void refreshContext(ConfigurableApplicationContext applicationContext) {
    refresh(applicationContext);
    if (this.registerShutdownHook) {
        try {
            applicationContext.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}
           

在 refreshContext() 方法中調用 refresh(applicationContext) 方法重新整理上下文。這個方法是 ApplicationContext 接口的核心方法,會啟動上下文,執行各種啟動任務。調用 registerShutdownHook() 方法注冊應用程式的關閉鈎子。這個方法會在應用程式關閉時自動執行,清理資源、關閉線程等,是以我們利用此特性在服務關閉的時候清理一些資源。并向外部發送告警通知。

在 refresh(applicationContext) 方法中,Spring Boot 會執行上下文的各種啟動任務,包括建立 Web 伺服器、加載應用程式的配置、初始化各種元件等。具體的啟動任務會調用各種初始化器和監聽器,例如:

for (ApplicationContextInitializer<?> initializer : getInitializers()) {
    initializer.initialize(applicationContext);
}
           

另外,Spring Boot 還會調用各種監聽器,我們不做贅述,例如:

for (ApplicationListener<?> listener : getApplicationListeners()) {
    if (listener instanceof SmartApplicationListener) {
        SmartApplicationListener smartListener = (SmartApplicationListener) listener;
        if (smartListener.supportsEventType(eventType)
                && smartListener.supportsSourceType(sourceType)) {
            invokeListener(smartListener, event);
        }
    }
    else if (supportsEvent(listener, eventType)) {
        invokeListener(listener, event);
    }
}
           

基本上就是這些了。

關于SpringApplication的官方文檔講的比較簡單,大家可供參考。位址如下:

https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application
三分鐘了解 SpringBoot 的啟動流程
來源:blog.csdn.net/wangshuai6707/article/details/131006060