1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        三分鐘了解 SpringBoot 的啟動流程

        共 15611字,需瀏覽 32分鐘

         ·

        2023-10-13 05:34


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


        前言


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

        這些問題,其實回答起來也是比較復(fù)雜的。我們今天就從 SpringApplication.run(EasyPaasAdminApplication.class, args);入口,逐漸向下看下執(zhí)行流程。來試著回答一下前面這兩個問題。

        后面關(guān)于SpringBoot 的web容器可以無縫隨意切換為jetty,undertow..這個問題的回答涉及到Spring Boot是如何設(shè)計WebServer的。我們后續(xù)專門講解一下。


        1、執(zhí)行邏輯梳理

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

        也可以是這樣

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

        但總之,都是使用SpringApplication 調(diào)用靜態(tài)方法

        此方法的注釋

        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應(yīng)用程序,創(chuàng)建并刷新一個新的ApplicationContext。

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

        到這兒我們就可以繼續(xù)下去,發(fā)現(xiàn)Spring Boot 啟動WebServer。此處的WebServer我就不展開了,可以點擊去就三個方法start ,stop,getPort。可以看出來Spring 在設(shè)計接口的時候還是很嚴(yán)謹(jǐn)和精簡。

        我們的核心脈絡(luò)是梳理SpringBoot 啟動過程,并且回答Tomcat 是如何被啟動的。

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

        這就是為什么5個實現(xiàn),我們最后啟動的是Tomcat。此處也不做展開。后面我專門搞一個解析SpringBoot 自動裝配的文章。

        我們看一下內(nèi)部start 的TomcatWebServer的內(nèi)部實現(xiàn)。了解過Tomcat 源碼的同學(xué)看到這兒就基本明白了。

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

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

        準(zhǔn)備階段、應(yīng)用上下文創(chuàng)建階段、刷新上下文階段。

        1. 準(zhǔn)備階段:Spring Boot 會加載應(yīng)用程序的初始設(shè)置,并創(chuàng)建 Spring Boot 上下文。這個階段的核心源碼是 SpringApplication 類的 run() 方法,它會調(diào)用 Spring Boot 的各個初始化器進行初始化和準(zhǔn)備工作。

        2. 應(yīng)用上下文創(chuàng)建階段 : Spring Boot 會創(chuàng)建應(yīng)用程序的上下文,包括各種配置信息、Bean 的加載和初始化等。這個階段的核心源碼是 Spring Boot 自動配置機制,通過掃描 classpath 中的配置文件,自動加載和配置各種組件和 Bean。

        3. 刷新上下文階段:Spring Boot 會執(zhí)行各種啟動任務(wù),包括創(chuàng)建 Web 服務(wù)器、加載應(yīng)用程序的配置、初始化各種組件等。這個階段的核心源碼是 Spring Boot 的刷新機制,它會調(diào)用各種初始化器和監(jiān)聽器,執(zhí)行各種啟動任務(wù)。其中啟動Tomcat 就是在這個環(huán)節(jié)進行。


        2、核心源碼解析

        既然上面我們已經(jīng)基本上總結(jié)除了,Spring Boot的啟動脈絡(luò)。也梳理出了一些核心源碼。那么我們對啟動過程的核心源碼解析一下。

        2.1. 準(zhǔn)備階段

        在準(zhǔn)備階段中,Spring Boot 會加載應(yīng)用程序的初始設(shè)置,并創(chuàng)建 Spring Boot 上下文。這個階段的核心源碼是 SpringApplication 類的 run() 方法,它會調(diào)用 Spring Boot 的各個初始化器進行初始化和準(zhǔn)備工作。

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

                         // 定義應(yīng)用程序上下文和異常報告器列表
                ConfigurableApplicationContext context = null;
                Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

                         // 配置 Headless 屬性
                configureHeadlessProperty();

                         // 獲取 Spring Boot 啟動監(jiān)聽器
                SpringApplicationRunListeners listeners = getRunListeners(args);
                         // 執(zhí)行啟動監(jiān)聽器的 starting 方法
                listeners.starting();

                try {
                         // 解析命令行參數(shù)
                    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                         // 準(zhǔn)備應(yīng)用程序環(huán)境
                    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
                         // 配置忽略 BeanInfo
                    configureIgnoreBeanInfo(environment);
                         // 打印 Banner
                    Banner printedBanner = printBanner(environment);
                         // 創(chuàng)建應(yīng)用程序上下文
                    context = createApplicationContext();
                         // 獲取異常報告器,關(guān)于異常報告,我下次專門講一下SpringBoot 的異常收集器。
                    exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.classnew Class[]{ConfigurableApplicationContext.class}, context);
                         // 準(zhǔn)備應(yīng)用程序上下文
                    prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                         // 刷新應(yīng)用程序上下文
                    refreshContext(context);
                         // 刷新后操作
                    afterRefresh(context, applicationArguments);
                         // 停止計時器
                    stopWatch.stop();
                         // 記錄啟動日志
                    if (this.logStartupInfo) {
                        new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
                    }
                         // 執(zhí)行啟動監(jiān)聽器的 started 方法
                    listeners.started(context);
                         // 執(zhí)行 Runner
                    callRunners(context, applicationArguments);
                } catch (Throwable ex) {
                         // 處理啟動失敗
                    handleRunFailure(context, ex, exceptionReporters, listeners);
                    throw new IllegalStateException(ex);
                }

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

                         // 返回應(yīng)用程序上下文
                return context;
            }

        run() 方法中,Spring Boot 首先會創(chuàng)建一個 StopWatch 對象,用于記錄整個啟動過程的耗時。然后,Spring Boot 會調(diào)用 getRunListeners(args) 方法獲取 Spring Boot 的各個啟動監(jiān)聽器,并調(diào)用starting() 方法通知這些監(jiān)聽器啟動過程已經(jīng)開始。接著調(diào)用 prepareEnvironment(listeners, applicationArguments) 方法創(chuàng)建應(yīng)用程序的環(huán)境變量。

        這個方法會根據(jù)用戶的配置和默認(rèn)設(shè)置創(chuàng)建一個 ConfigurableEnvironment對象,并將其傳給后面的 createApplicationContext() 方法。printBanner(environment) 方法打印啟動界面的 Banner,調(diào)用 refreshContext(context)方法刷新上下文。這個方法會啟動上下文,執(zhí)行各種啟動任務(wù),包括創(chuàng)建 Web 服務(wù)器、加載應(yīng)用程序的配置、初始化各種組件等。具體的啟動任務(wù)會在刷新上下文階段中進行。

        2.2. 應(yīng)用上下文創(chuàng)建階段

        在應(yīng)用上下文創(chuàng)建階段中,Spring Boot 會創(chuàng)建應(yīng)用程序的上下文,包括各種配置信息、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 首先會判斷應(yīng)用程序的類型,如果是 Web 應(yīng)用程序,則會創(chuàng)建一個 WebApplicationContext;否則,會創(chuàng)建一個普通的 ApplicationContext。調(diào)用 BeanUtils.instantiateClass(contextClass) 方法創(chuàng)建應(yīng)用程序的上下文。這個方法會根據(jù)上面的邏輯創(chuàng)建一個相應(yīng)的 ApplicationContext。調(diào)用 load() 方法加載應(yīng)用程序的配置。

        這個方法會掃描 classpath 中的各種配置文件,例如 application.properties、application.ymlMETA-INF/spring.factories 等,自動配置各種組件和 Bean。調(diào)用 postProcessApplicationContext() 方法對應(yīng)用程序的上下文進行后處理。這個方法會調(diào)用各種初始化器和監(jiān)聽器,執(zhí)行各種初始化任務(wù)。

        2.3. 刷新上下文階段

        在刷新上下文階段中,Spring Boot 會執(zhí)行各種啟動任務(wù),包括創(chuàng)建 Web 服務(wù)器(剛才我們跟源碼的時候也看到了,如上我的截圖)、加載應(yīng)用程序的配置、初始化各種組件等。這個階段的核心源碼是 Spring Boot 的刷新機制,它會調(diào)用各種初始化器和監(jiān)聽器,執(zhí)行各種啟動任務(wù)。

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

        refreshContext() 方法中調(diào)用 refresh(applicationContext) 方法刷新上下文。這個方法是 ApplicationContext 接口的核心方法,會啟動上下文,執(zhí)行各種啟動任務(wù)。調(diào)用 registerShutdownHook() 方法注冊應(yīng)用程序的關(guān)閉鉤子。這個方法會在應(yīng)用程序關(guān)閉時自動執(zhí)行,清理資源、關(guān)閉線程等,所以我們利用此特性在服務(wù)關(guān)閉的時候清理一些資源。并向外部發(fā)送告警通知。

        refresh(applicationContext) 方法中,Spring Boot 會執(zhí)行上下文的各種啟動任務(wù),包括創(chuàng)建 Web 服務(wù)器、加載應(yīng)用程序的配置、初始化各種組件等。具體的啟動任務(wù)會調(diào)用各種初始化器和監(jiān)聽器,例如:

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

        另外,Spring Boot 還會調(diào)用各種監(jiān)聽器,我們不做贅述,例如:

        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);
            }
        }

        基本上就是這些了。

        關(guān)于SpringApplication的官方文檔講的比較簡單,大家可供參考。地址如下:

        • https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application


         你在看嗎


        瀏覽 1610
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美xxxxxx视频 | 《教室爱欲》无删减韩国电影 | 美女被c在线观看视频 | 色色网站在线观看 | free性欧美hd精品4k | 办公室高h荡肉呻吟在线观看 | 久久国产成人午夜AV影院潦草 | 国产午夜性爽视频男人的天堂 | 三洞齐开国语版免费观看高清 | 99re6国产亚洲这里只有精品 |