0.前言
背景:最近有位開(kāi)發(fā)同學(xué)說(shuō)面試被問(wèn)到Spring Boot 的啟動(dòng)流程,以及被問(wèn)到Spring Boot 的嵌入式Web容器是什么時(shí)候加載的。如何加載的。是怎么無(wú)縫切換的。
這些問(wèn)題,其實(shí)回答起來(lái)也是比較復(fù)雜的。我們今天就從 SpringApplication.run(EasyPaasAdminApplication.class, args);入口,逐漸向下看下執(zhí)行流程。來(lái)試著回答一下前面這兩個(gè)問(wèn)題。
后面關(guān)于SpringBoot 的web容器可以無(wú)縫隨意切換為jetty,undertow..這個(gè)問(wèn)題的回答涉及到Spring Boot是如何設(shè)計(jì)WebServer的。我們后續(xù)專門(mén)講解一下。
1. 執(zhí)行邏輯梳理
一般我們SpringBoot 應(yīng)用的啟動(dòng)入口都是如下這種固定的寫(xiě)法,
也可以是這樣
publicstaticvoidmain(String[]args){ SpringApplicationapplication=newSpringApplication(MyApplication.class); //...customizeapplicationsettingshere 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.
publicstaticConfigurableApplicationContextrun(Class>primarySource,String...args){ returnrun(newClass>[]{primarySource},args); }
跟過(guò)來(lái)就到這,可以看到注釋運(yùn)行Spring應(yīng)用程序,創(chuàng)建并刷新一個(gè)新的ApplicationContext。
跟代碼到這兒其實(shí)我們對(duì)于SpringBoot 的基本啟動(dòng)流程已經(jīng)知道了。但是要解答什么時(shí)候啟動(dòng)的Tomcat 還需要繼續(xù)分析。
到這兒我們就可以繼續(xù)下去,發(fā)現(xiàn)Spring Boot 啟動(dòng)WebServer。此處的WebServer我就不展開(kāi)了,可以點(diǎn)擊去就三個(gè)方法start ,stop,getPort??梢钥闯鰜?lái)Spring 在設(shè)計(jì)接口的時(shí)候還是很嚴(yán)謹(jǐn)和精簡(jiǎn)。
我們的核心脈絡(luò)是梳理SpringBoot 啟動(dòng)過(guò)程,并且回答Tomcat 是如何被啟動(dòng)的。
我們可以看到WebServer 的實(shí)現(xiàn)目前內(nèi)置的有5種。其實(shí)Spring Boot 還有一個(gè)特性叫做 自動(dòng)裝配。
這就是為什么5個(gè)實(shí)現(xiàn),我們最后啟動(dòng)的是Tomcat。此處也不做展開(kāi)。后面我專門(mén)搞一個(gè)解析SpringBoot 自動(dòng)裝配的文章。
我們看一下內(nèi)部start 的TomcatWebServer的內(nèi)部實(shí)現(xiàn)。了解過(guò)Tomcat 源碼的同學(xué)看到這兒就基本明白了。
好源碼跟進(jìn)過(guò)程我們到此結(jié)束,我們整理和總結(jié)一下。
通過(guò)掃一遍源碼我們大概可以總結(jié)出來(lái)如下三個(gè)階段
準(zhǔn)備階段、應(yīng)用上下文創(chuàng)建階段、刷新上下文階段。
準(zhǔn)備階段 :Spring Boot 會(huì)加載應(yīng)用程序的初始設(shè)置,并創(chuàng)建 Spring Boot 上下文。這個(gè)階段的核心源碼是 SpringApplication 類的 run() 方法,它會(huì)調(diào)用 Spring Boot 的各個(gè)初始化器進(jìn)行初始化和準(zhǔn)備工作。
應(yīng)用上下文創(chuàng)建階段 : Spring Boot 會(huì)創(chuàng)建應(yīng)用程序的上下文,包括各種配置信息、Bean 的加載和初始化等。這個(gè)階段的核心源碼是 Spring Boot 自動(dòng)配置機(jī)制,通過(guò)掃描 classpath 中的配置文件,自動(dòng)加載和配置各種組件和 Bean。
刷新上下文階段 :Spring Boot 會(huì)執(zhí)行各種啟動(dòng)任務(wù),包括創(chuàng)建 Web 服務(wù)器、加載應(yīng)用程序的配置、初始化各種組件等。這個(gè)階段的核心源碼是 Spring Boot 的刷新機(jī)制,它會(huì)調(diào)用各種初始化器和監(jiān)聽(tīng)器,執(zhí)行各種啟動(dòng)任務(wù)。其中啟動(dòng)Tomcat 就是在這個(gè)環(huán)節(jié)進(jìn)行。
2. 核心源碼解析
既然上面我們已經(jīng)基本上總結(jié)除了,Spring Boot的啟動(dòng)脈絡(luò)。也梳理出了一些核心源碼。那么我們對(duì)啟動(dòng)過(guò)程的核心源碼解析一下。
2.1. 準(zhǔn)備階段
在準(zhǔn)備階段中,Spring Boot 會(huì)加載應(yīng)用程序的初始設(shè)置,并創(chuàng)建 Spring Boot 上下文。這個(gè)階段的核心源碼是 SpringApplication 類的 run() 方法,它會(huì)調(diào)用 Spring Boot 的各個(gè)初始化器進(jìn)行初始化和準(zhǔn)備工作。
publicConfigurableApplicationContextrun(String...args){ //啟動(dòng)計(jì)時(shí)器 StopWatchstopWatch=newStopWatch(); stopWatch.start(); //定義應(yīng)用程序上下文和異常報(bào)告器列表 ConfigurableApplicationContextcontext=null; CollectionexceptionReporters=newArrayList<>(); //配置Headless屬性 configureHeadlessProperty(); //獲取SpringBoot啟動(dòng)監(jiān)聽(tīng)器 SpringApplicationRunListenerslisteners=getRunListeners(args); //執(zhí)行啟動(dòng)監(jiān)聽(tīng)器的starting方法 listeners.starting(); try{ //解析命令行參數(shù) ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(args); //準(zhǔn)備應(yīng)用程序環(huán)境 ConfigurableEnvironmentenvironment=prepareEnvironment(listeners,applicationArguments); //配置忽略BeanInfo configureIgnoreBeanInfo(environment); //打印Banner BannerprintedBanner=printBanner(environment); //創(chuàng)建應(yīng)用程序上下文 context=createApplicationContext(); //獲取異常報(bào)告器,關(guān)于異常報(bào)告,我下次專門(mén)講一下SpringBoot 的異常收集器。 exceptionReporters=getSpringFactoriesInstances(SpringBootExceptionReporter.class,newClass[]{ConfigurableApplicationContext.class},context); //準(zhǔn)備應(yīng)用程序上下文 prepareContext(context,environment,listeners,applicationArguments,printedBanner); //刷新應(yīng)用程序上下文 refreshContext(context); //刷新后操作 afterRefresh(context,applicationArguments); //停止計(jì)時(shí)器 stopWatch.stop(); //記錄啟動(dòng)日志 if(this.logStartupInfo){ newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),stopWatch); } //執(zhí)行啟動(dòng)監(jiān)聽(tīng)器的started方法 listeners.started(context); //執(zhí)行Runner callRunners(context,applicationArguments); }catch(Throwableex){ //處理啟動(dòng)失敗 handleRunFailure(context,ex,exceptionReporters,listeners); thrownewIllegalStateException(ex); } try{ //執(zhí)行啟動(dòng)監(jiān)聽(tīng)器的running方法 listeners.running(context); }catch(Throwableex){ //處理啟動(dòng)失敗 handleRunFailure(context,ex,exceptionReporters,null); thrownewIllegalStateException(ex); } //返回應(yīng)用程序上下文 returncontext; }
在 run() 方法中,Spring Boot 首先會(huì)創(chuàng)建一個(gè) StopWatch 對(duì)象,用于記錄整個(gè)啟動(dòng)過(guò)程的耗時(shí)。然后,Spring Boot 會(huì)調(diào)用 getRunListeners(args) 方法獲取 Spring Boot 的各個(gè)啟動(dòng)監(jiān)聽(tīng)器,并調(diào)用starting() 方法通知這些監(jiān)聽(tīng)器啟動(dòng)過(guò)程已經(jīng)開(kāi)始。接著調(diào)用 prepareEnvironment(listeners, applicationArguments) 方法創(chuàng)建應(yīng)用程序的環(huán)境變量。
這個(gè)方法會(huì)根據(jù)用戶的配置和默認(rèn)設(shè)置創(chuàng)建一個(gè) ConfigurableEnvironment對(duì)象,并將其傳給后面的 createApplicationContext() 方法。printBanner(environment) 方法打印啟動(dòng)界面的 Banner,調(diào)用 refreshContext(context)方法刷新上下文。這個(gè)方法會(huì)啟動(dòng)上下文,執(zhí)行各種啟動(dòng)任務(wù),包括創(chuàng)建 Web 服務(wù)器、加載應(yīng)用程序的配置、初始化各種組件等。具體的啟動(dòng)任務(wù)會(huì)在刷新上下文階段中進(jìn)行。
2.2. 應(yīng)用上下文創(chuàng)建階段
在應(yīng)用上下文創(chuàng)建階段中,Spring Boot 會(huì)創(chuàng)建應(yīng)用程序的上下文,包括各種配置信息、Bean 的加載和初始化等。這個(gè)階段的核心源碼是 Spring Boot 自動(dòng)配置機(jī)制,通過(guò)掃描 classpath 中的配置文件,自動(dòng)加載和配置各種組件和 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 首先會(huì)判斷應(yīng)用程序的類型,如果是 Web 應(yīng)用程序,則會(huì)創(chuàng)建一個(gè) WebApplicationContext;否則,會(huì)創(chuàng)建一個(gè)普通的 ApplicationContext。調(diào)用 BeanUtils.instantiateClass(contextClass) 方法創(chuàng)建應(yīng)用程序的上下文。這個(gè)方法會(huì)根據(jù)上面的邏輯創(chuàng)建一個(gè)相應(yīng)的 ApplicationContext。調(diào)用 load() 方法加載應(yīng)用程序的配置。
這個(gè)方法會(huì)掃描 classpath 中的各種配置文件,例如 application.properties、application.yml、META-INF/spring.factories 等,自動(dòng)配置各種組件和 Bean。調(diào)用 postProcessApplicationContext() 方法對(duì)應(yīng)用程序的上下文進(jìn)行后處理。這個(gè)方法會(huì)調(diào)用各種初始化器和監(jiān)聽(tīng)器,執(zhí)行各種初始化任務(wù)。
2.3. 刷新上下文階段
在刷新上下文階段中,Spring Boot 會(huì)執(zhí)行各種啟動(dòng)任務(wù),包括創(chuàng)建 Web 服務(wù)器(剛才我們跟源碼的時(shí)候也看到了,如上我的截圖)、加載應(yīng)用程序的配置、初始化各種組件等。這個(gè)階段的核心源碼是 Spring Boot 的刷新機(jī)制,它會(huì)調(diào)用各種初始化器和監(jiān)聽(tīng)器,執(zhí)行各種啟動(dòng)任務(wù)。
protectedvoidrefreshContext(ConfigurableApplicationContextapplicationContext){ refresh(applicationContext); if(this.registerShutdownHook){ try{ applicationContext.registerShutdownHook(); } catch(AccessControlExceptionex){ //Notallowedinsomeenvironments. } } }
在 refreshContext() 方法中調(diào)用 refresh(applicationContext) 方法刷新上下文。這個(gè)方法是 ApplicationContext 接口的核心方法,會(huì)啟動(dòng)上下文,執(zhí)行各種啟動(dòng)任務(wù)。調(diào)用 registerShutdownHook() 方法注冊(cè)應(yīng)用程序的關(guān)閉鉤子。這個(gè)方法會(huì)在應(yīng)用程序關(guān)閉時(shí)自動(dòng)執(zhí)行,清理資源、關(guān)閉線程等,所以我們利用此特性在服務(wù)關(guān)閉的時(shí)候清理一些資源。并向外部發(fā)送告警通知。
在 refresh(applicationContext) 方法中,Spring Boot 會(huì)執(zhí)行上下文的各種啟動(dòng)任務(wù),包括創(chuàng)建 Web 服務(wù)器、加載應(yīng)用程序的配置、初始化各種組件等。具體的啟動(dòng)任務(wù)會(huì)調(diào)用各種初始化器和監(jiān)聽(tīng)器,例如:
for(ApplicationContextInitializer>initializer:getInitializers()){ initializer.initialize(applicationContext); }
另外,Spring Boot 還會(huì)調(diào)用各種監(jiān)聽(tīng)器,我們不做贅述,例如:
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); } }
基本上就是這些了。
審核編輯:劉清
-
嵌入式
+關(guān)注
關(guān)注
5094文章
19183瀏覽量
307753 -
Web服務(wù)器
+關(guān)注
關(guān)注
0文章
138瀏覽量
24488 -
tomcat
+關(guān)注
關(guān)注
0文章
30瀏覽量
4871 -
SpringBoot
+關(guān)注
關(guān)注
0文章
174瀏覽量
201
原文標(biāo)題:三分鐘了解 SpringBoot 的啟動(dòng)流程
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論