三分鐘瞭解 Spring Boot 的啓動流程

作者:算. 子

原文:https://blog.csdn.net/wangshuai6707/article/details/131006060

0. 前言

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

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

1. 執行邏輯梳理

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

也可以是這樣

但總之,都是使用 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 的基本啓動流程已經知道了。但是要解答什麼時候啓動的 Tomcat 還需要繼續分析。

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

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

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

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

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

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

準備階段、應用上下文創建階段、刷新上下文階段。

  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.propertiesapplication.ymlMETA-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

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/kBuguYKdq2nR-2-JXW-odw