0.前言
背景:最近有位開發同學說面試被問到Spring Boot 的啟動流程,以及被問到Spring Boot 的嵌入式Web容器是什么時候加載的。如何加載的。是怎么無縫切換的。
這些問題,其實回答起來也是比較復雜的。我們今天就從 SpringApplication.run(EasyPaasAdminApplication.class, args);入口,逐漸向下看下執行流程。來試著回答一下前面這兩個問題。
后面關于SpringBoot 的web容器可以無縫隨意切換為jetty,undertow..這個問題的回答涉及到Spring Boot是如何設計WebServer的。我們后續專門講解一下。
1. 執行邏輯梳理
一般我們SpringBoot 應用的啟動入口都是如下這種固定的寫法,
也可以是這樣
publicstaticvoidmain(String[]args){ SpringApplicationapplication=newSpringApplication(MyApplication.class); //...customizeapplicationsettingshere application.run(args) }
但總之,都是使用SpringApplication 調用靜態方法
此方法的注釋
Static helper that can be used to run a SpringApplication from the specified source using default settings.
publicstaticConfigurableApplicationContextrun(Class>primarySource,String...args){ returnrun(newClass>[]{primarySource},args); }
跟過來就到這,可以看到注釋運行Spring應用程序,創建并刷新一個新的ApplicationContext。
跟代碼到這兒其實我們對于SpringBoot 的基本啟動流程已經知道了。但是要解答什么時候啟動的Tomcat 還需要繼續分析。
到這兒我們就可以繼續下去,發現Spring Boot 啟動WebServer。此處的WebServer我就不展開了,可以點擊去就三個方法start ,stop,getPort。可以看出來Spring 在設計接口的時候還是很嚴謹和精簡。
我們的核心脈絡是梳理SpringBoot 啟動過程,并且回答Tomcat 是如何被啟動的。
我們可以看到WebServer 的實現目前內置的有5種。其實Spring Boot 還有一個特性叫做 自動裝配。
這就是為什么5個實現,我們最后啟動的是Tomcat。此處也不做展開。后面我專門搞一個解析SpringBoot 自動裝配的文章。
我們看一下內部start 的TomcatWebServer的內部實現。了解過Tomcat 源碼的同學看到這兒就基本明白了。
好源碼跟進過程我們到此結束,我們整理和總結一下。
通過掃一遍源碼我們大概可以總結出來如下三個階段
準備階段、應用上下文創建階段、刷新上下文階段。
準備階段 :Spring Boot 會加載應用程序的初始設置,并創建 Spring Boot 上下文。這個階段的核心源碼是 SpringApplication 類的 run() 方法,它會調用 Spring Boot 的各個初始化器進行初始化和準備工作。
應用上下文創建階段 : Spring Boot 會創建應用程序的上下文,包括各種配置信息、Bean 的加載和初始化等。這個階段的核心源碼是 Spring Boot 自動配置機制,通過掃描 classpath 中的配置文件,自動加載和配置各種組件和 Bean。
刷新上下文階段 :Spring Boot 會執行各種啟動任務,包括創建 Web 服務器、加載應用程序的配置、初始化各種組件等。這個階段的核心源碼是 Spring Boot 的刷新機制,它會調用各種初始化器和監聽器,執行各種啟動任務。其中啟動Tomcat 就是在這個環節進行。
2. 核心源碼解析
既然上面我們已經基本上總結除了,Spring Boot的啟動脈絡。也梳理出了一些核心源碼。那么我們對啟動過程的核心源碼解析一下。
2.1. 準備階段
在準備階段中,Spring Boot 會加載應用程序的初始設置,并創建 Spring Boot 上下文。這個階段的核心源碼是 SpringApplication 類的 run() 方法,它會調用 Spring Boot 的各個初始化器進行初始化和準備工作。
publicConfigurableApplicationContextrun(String...args){ //啟動計時器 StopWatchstopWatch=newStopWatch(); stopWatch.start(); //定義應用程序上下文和異常報告器列表 ConfigurableApplicationContextcontext=null; CollectionexceptionReporters=newArrayList<>(); //配置Headless屬性 configureHeadlessProperty(); //獲取SpringBoot啟動監聽器 SpringApplicationRunListenerslisteners=getRunListeners(args); //執行啟動監聽器的starting方法 listeners.starting(); try{ //解析命令行參數 ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(args); //準備應用程序環境 ConfigurableEnvironmentenvironment=prepareEnvironment(listeners,applicationArguments); //配置忽略BeanInfo configureIgnoreBeanInfo(environment); //打印Banner BannerprintedBanner=printBanner(environment); //創建應用程序上下文 context=createApplicationContext(); //獲取異常報告器,關于異常報告,我下次專門講一下SpringBoot 的異常收集器。 exceptionReporters=getSpringFactoriesInstances(SpringBootExceptionReporter.class,newClass[]{ConfigurableApplicationContext.class},context); //準備應用程序上下文 prepareContext(context,environment,listeners,applicationArguments,printedBanner); //刷新應用程序上下文 refreshContext(context); //刷新后操作 afterRefresh(context,applicationArguments); //停止計時器 stopWatch.stop(); //記錄啟動日志 if(this.logStartupInfo){ newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),stopWatch); } //執行啟動監聽器的started方法 listeners.started(context); //執行Runner callRunners(context,applicationArguments); }catch(Throwableex){ //處理啟動失敗 handleRunFailure(context,ex,exceptionReporters,listeners); thrownewIllegalStateException(ex); } try{ //執行啟動監聽器的running方法 listeners.running(context); }catch(Throwableex){ //處理啟動失敗 handleRunFailure(context,ex,exceptionReporters,null); thrownewIllegalStateException(ex); } //返回應用程序上下文 returncontext; }
在 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。
protectedConfigurableApplicationContextcreateApplicationContext(){ Class>contextClass=this.applicationContextClass; if(contextClass==null){ try{ switch(this.webApplicationType){ caseSERVLET: contextClass=Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break; caseREACTIVE: contextClass=Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break; default: contextClass=Class.forName(DEFAULT_CONTEXT_CLASS); } } catch(ClassNotFoundExceptionex){ thrownewIllegalStateException( "UnabletocreateadefaultApplicationContext,"+ "pleasespecifyanApplicationContextClass",ex); } } return(ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass); }
在 createApplicationContext() 方法中,Spring Boot 首先會判斷應用程序的類型,如果是 Web 應用程序,則會創建一個 WebApplicationContext;否則,會創建一個普通的 ApplicationContext。調用 BeanUtils.instantiateClass(contextClass) 方法創建應用程序的上下文。這個方法會根據上面的邏輯創建一個相應的 ApplicationContext。調用 load() 方法加載應用程序的配置。
這個方法會掃描 classpath 中的各種配置文件,例如 application.properties、application.yml、META-INF/spring.factories 等,自動配置各種組件和 Bean。調用 postProcessApplicationContext() 方法對應用程序的上下文進行后處理。這個方法會調用各種初始化器和監聽器,執行各種初始化任務。
2.3. 刷新上下文階段
在刷新上下文階段中,Spring Boot 會執行各種啟動任務,包括創建 Web 服務器(剛才我們跟源碼的時候也看到了,如上我的截圖)、加載應用程序的配置、初始化各種組件等。這個階段的核心源碼是 Spring Boot 的刷新機制,它會調用各種初始化器和監聽器,執行各種啟動任務。
protectedvoidrefreshContext(ConfigurableApplicationContextapplicationContext){ refresh(applicationContext); if(this.registerShutdownHook){ try{ applicationContext.registerShutdownHook(); } catch(AccessControlExceptionex){ //Notallowedinsomeenvironments. } } }
在 refreshContext() 方法中調用 refresh(applicationContext) 方法刷新上下文。這個方法是 ApplicationContext 接口的核心方法,會啟動上下文,執行各種啟動任務。調用 registerShutdownHook() 方法注冊應用程序的關閉鉤子。這個方法會在應用程序關閉時自動執行,清理資源、關閉線程等,所以我們利用此特性在服務關閉的時候清理一些資源。并向外部發送告警通知。
在 refresh(applicationContext) 方法中,Spring Boot 會執行上下文的各種啟動任務,包括創建 Web 服務器、加載應用程序的配置、初始化各種組件等。具體的啟動任務會調用各種初始化器和監聽器,例如:
for(ApplicationContextInitializer>initializer:getInitializers()){ initializer.initialize(applicationContext); }
另外,Spring Boot 還會調用各種監聽器,我們不做贅述,例如:
for(ApplicationListener>listener:getApplicationListeners()){ if(listenerinstanceofSmartApplicationListener){ SmartApplicationListenersmartListener=(SmartApplicationListener)listener; if(smartListener.supportsEventType(eventType) &&smartListener.supportsSourceType(sourceType)){ invokeListener(smartListener,event); } } elseif(supportsEvent(listener,eventType)){ invokeListener(listener,event); } }
基本上就是這些了。
審核編輯:劉清
-
嵌入式
+關注
關注
5068文章
19019瀏覽量
303291 -
Web服務器
+關注
關注
0文章
138瀏覽量
24371 -
tomcat
+關注
關注
0文章
27瀏覽量
4845 -
SpringBoot
+關注
關注
0文章
173瀏覽量
169
原文標題:三分鐘了解 SpringBoot 的啟動流程
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論