1. 程式人生 > 實用技巧 >springboot啟動類原始碼探索一波

springboot啟動類原始碼探索一波

原始碼如何切分?

SpringApplication中的靜態run()方法並不是一步完成的,最終執行的原始碼如下:

//org.springframework.context.ConfigurableApplicationContext
publicstaticConfigurableApplicationContextrun(Class<?>[]primarySources,String[]args){
returnnewSpringApplication(primarySources).run(args);
}

很顯然分為兩個步驟,分別是建立SpringApplication和執行run()

方法,下面將分為這兩個部分介紹。

如何建立SpringApplication?

建立即是new物件了,DEBUG跟進程式碼,最終執行的SpringApplication構造方法如下圖:

如上圖中標註的註釋,建立過程重用的其實分為這三個階段,下面將會一一介紹每個階段做了什麼事。

設定應用型別

這個過程非常重要,直接決定了專案的型別,應用型別分為三種,都在WebApplicationType這個列舉類中,如下:

  1. NONE:顧名思義,什麼都沒有,正常流程走,不額外的啟動web容器,比如Tomcat
  2. SERVLET:基於servlet的web程式,需要啟動內嵌的servletweb容器,比如Tomcat
  3. REACTIVE:基於reactive的web程式,需要啟動內嵌reactiveweb容器,作者不是很瞭解,不便多說。

判斷的依據很簡單,就是載入對應的類,比如載入了DispatcherServlet等則會判斷是Servlet的web程式。原始碼如下:

staticWebApplicationTypededuceFromClasspath(){
if(ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS,null)&&!ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS,null)
&&!ClassUtils.isPresent(JERSEY_INDICATOR_CLASS,null)){
returnWebApplicationType.REACTIVE;
}
for(StringclassName:SERVLET_INDICATOR_CLASSES){
if(!ClassUtils.isPresent(className,null)){
returnWebApplicationType.NONE;
}
}
returnWebApplicationType.SERVLET;
}

這裡我引入了spring-boot-starter-web,肯定是Servlet的web程式。

設定初始化器(Initializer)

初始化器ApplicationContextInitializer是個好東西,用於IOC容器重新整理之前初始化一些元件,比如ServletContextApplicationContextInitializer

那麼如何獲取初始化器呢?跟著上圖中的程式碼進入,在SpringApplication中的如下圖中的方法:

相對重要的就是第一步獲取初始化器的名稱了,這個肯定是全類名了,詳細原始碼肯定在loadFactoryNames()方法中了,跟著原始碼進入,最終呼叫的是#SpringFactoriesLoader.loadSpringFactories()方法。

loadSpringFactories()方法就不再詳細解釋了,其實就是從類路徑META-INF/spring.factories中載入ApplicationContextInitializer的值。

spring-boot-autoconfigurespring.factories檔案中的值如下圖:

上圖中的只是一部分初始化器,因為spring.factories檔案不止一個。

下圖中是我的demo中注入的初始化器,現實專案中並不止這些。

這也告訴我們自定義一個ApplicationContextInitializer只需要實現介面,在spring.factories檔案中設定即可。

設定監聽器(Listener)

監聽器(ApplicationListener)這個概念在Spring中就已經存在,主要用於監聽特定的事件(ApplicationEvent),比如IOC容器重新整理、容器關閉等。

Spring Boot擴充套件了ApplicationEvent構建了SpringApplicationEvent這個抽象類,主要用於Spring Boot啟動過程中觸發的事件,比如程式啟動中、程式啟動完成等。如下圖:

監聽器如何獲取?從原始碼中知道其實和初始化器(ApplicationContextInitializer)執行的是同一個方法,同樣是從META-INF/spring.factories檔案中獲取。

spring-boot-autoconfigurespring.factories檔案中的值如下圖:

spring.factories檔案不止一個,同樣監聽器也不止以上這些。

作者demo中注入的一些監聽器如下圖:

總結

SpringApplication的構建都是為了run()方法啟動做鋪墊,構造方法中總共就有幾行程式碼,最重要的部分就是設定應用型別、設定初始化器、設定監聽器。

「注意」:初始化器和這裡的監聽器都要放置在spring.factories檔案中才能在這一步驟載入,否則不會生效,因此此時IOC容器還未建立,即使將其注入到IOC容器中也是不會生效的。

作者簡單的畫了張執行流程圖,僅供參考,如下:

執行run()方法

上面分析了SpringApplication的構建過程,一切都做好了鋪墊,現在到了啟動的過程了。

作者根據原始碼將啟動過程分為了「8步」,下面將會一一介紹。

1. 獲取、啟動執行過程監聽器

SpringApplicationRunListener這個監聽器和ApplicationListener不同,它是用來監聽應用程式啟動過程的,介面的各個方法含義如下:

publicinterfaceSpringApplicationRunListener{

//在run()方法開始執行時,該方法就立即被呼叫,可用於在初始化最早期時做一些工作
voidstarting();
//當environment構建完成,ApplicationContext建立之前,該方法被呼叫
voidenvironmentPrepared(ConfigurableEnvironmentenvironment);
//當ApplicationContext構建完成時,該方法被呼叫
voidcontextPrepared(ConfigurableApplicationContextcontext);
//在ApplicationContext完成載入,但沒有被重新整理前,該方法被呼叫
voidcontextLoaded(ConfigurableApplicationContextcontext);
//在ApplicationContext重新整理並啟動後,CommandLineRunners和ApplicationRunner未被呼叫前,該方法被呼叫
voidstarted(ConfigurableApplicationContextcontext);
//在run()方法執行完成前該方法被呼叫
voidrunning(ConfigurableApplicationContextcontext);
//當應用執行出錯時該方法被呼叫
voidfailed(ConfigurableApplicationContextcontext,Throwableexception);
}

如何獲取執行監聽器?

SpringApplication#run()方法中,原始碼如下:

//從spring.factories中獲取監聽器
SpringApplicationRunListenerslisteners=getRunListeners(args);

跟進getRunListeners()方法,其實還是呼叫了loadFactoryNames()方法從spring.factories檔案中獲取值,如下:

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

最終注入的是EventPublishingRunListener這個實現類,建立例項過程肯定是通過反射了,因此我們看看它的構造方法,如下圖:

這個執行監聽器內部有一個事件廣播器(SimpleApplicationEventMulticaster),主要用來廣播特定的事件(SpringApplicationEvent)來觸發特定的監聽器ApplicationListener

EventPublishingRunListener中的每個方法用來觸發SpringApplicationEvent中的不同子類。

如何啟動執行監聽器?

SpringApplication#run()方法中,原始碼如下:

//執行starting()方法
listeners.starting(bootstrapContext,this.mainApplicationClass);

執行SpringApplicationRunListenersstarting()方法,跟進去其實很簡單,遍歷執行上面獲取的執行監聽器,這裡只有一個EventPublishingRunListener。因此執行的是它的starting()方法,原始碼如下圖:

上述原始碼中邏輯很簡單,其實只是執行了multicastEvent()方法,廣播了ApplicationStartingEvent事件。至於multicastEvent()內部方法感興趣的可以看看,其實就是遍歷ApplicationListener的實現類,找到監聽ApplicationStartingEvent這個事件的監聽器,執行onApplicationEvent()方法。

總結

這一步其實就是廣播了ApplicationStartingEvent事件來觸發監聽這個事件的ApplicationListener

因此如果自定義了ApplicationListener並且監聽了ApplicationStartingEvent(應用程式開始啟動)事件,則這個監聽器將會被觸發。

2. 環境構建

這一步主要用於載入系統配置以及使用者的自定義配置(application.properties),原始碼如下,在run()方法中:

ConfigurableEnvironmentenvironment=prepareEnvironment(listeners,bootstrapContext,applicationArguments);

prepareEnvironment方法內部廣播了ApplicationEnvironmentPreparedEvent事件,原始碼如下圖:

環境構建這一步載入了系統環境配置、使用者自定義配置並且廣播了ApplicationEnvironmentPreparedEvent事件,觸發監聽器。

3. 建立IOC容器

原始碼在run()方法中,如下:

context=createApplicationContext();

跟進程式碼,真正執行的是ApplicationContextFactory方法,如下圖:

根據webApplicationType決定建立的型別,很顯然,我這裡的是servlet,因此建立的是AnnotationConfigServletWebServerApplicationContext

這一步僅僅是建立了IOC容器,未有其他操作。

4. IOC容器的前置處理

這一步真是精華了,在重新整理容器之前做準備,其中有一個非常關鍵的操作:將啟動類注入容器,為後續的自動化配置奠定基礎。原始碼如下:

prepareContext(context,environment,listeners,applicationArguments,printedBanner);

prepareContext()原始碼解析如下圖,內容還是挺多的:

從上圖可以看出步驟很多,下面將會詳細介紹幾個重點的內容。

呼叫初始化器

SpringApplication構建過程中設定的初始化器,從spring.factories取值的。執行的流程很簡單,遍歷執行,原始碼如下圖:

將自定義的ApplicationContextInitializer放在META-INF/spring.factories中,在此時也是會被呼叫。

載入啟動類,注入容器

這一步是將主啟動類載入到IOC容器中,作為後續自動配置的入口。

SpringApplication構建過程中將主啟動類放置在primarySources這個集合中,此時的getAllSources()即是從其中取值,如下圖:

這裡取出的就是主啟動類,當然你的專案中可能不止一個,接下來就是將其載入到IOC容器中了,原始碼如下:

load(context,sources.toArray(newObject[0]));

跟著程式碼進去,其實主要邏輯都在BeanDefinitionLoader.load()方法,如下圖:

將主啟動類載入到beanDefinitionMap,後續該啟動類將作為開啟自動配置化的入口,後續章節詳細介紹。

兩次廣播事件

這一步涉及到了兩次事件廣播,分別是ApplicationContextInitializedEventApplicationPreparedEvent,對應的原始碼如下:

listeners.contextPrepared(context);
load(context,sources.toArray(newObject[0]));

5. 重新整理容器

重新整理容器完全是Spring的功能了,比如初始化資源,初始化上下文廣播器等,這個就不再詳細介紹,有興趣可以看看Spring的原始碼。

protectedvoidrefresh(ApplicationContextapplicationContext){
Assert.isInstanceOf(AbstractApplicationContext.class,applicationContext);
//呼叫建立的容器applicationContext中的refresh()方法
((AbstractApplicationContext)applicationContext).refresh();
}
publicvoidrefresh()throwsBeansException,IllegalStateException{
synchronized(this.startupShutdownMonitor){
/**
*重新整理上下文環境
*/
prepareRefresh();

/**
*初始化BeanFactory,解析XML,相當於之前的XmlBeanFactory的操作,
*/
ConfigurableListableBeanFactorybeanFactory=obtainFreshBeanFactory();

/**
*為上下文準備BeanFactory,即對BeanFactory的各種功能進行填充,如常用的註解@Autowired@Qualifier
*新增ApplicationContextAwareProcessor處理器
*在依賴注入忽略實現*Aware的介面,如EnvironmentAware、ApplicationEventPublisherAware等
*註冊依賴,如一個bean的屬性中含有ApplicationEventPublisher(beanFactory),則會將beanFactory的例項注入進去
*/
prepareBeanFactory(beanFactory);

try{
/**
*提供子類覆蓋的額外處理,即子類處理自定義的BeanFactoryPostProcess
*/
postProcessBeanFactory(beanFactory);

/**
*啟用各種BeanFactory處理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
*執行對應的postProcessBeanDefinitionRegistry方法和postProcessBeanFactory方法
*/
invokeBeanFactoryPostProcessors(beanFactory);

/**
*註冊攔截Bean建立的Bean處理器,即註冊BeanPostProcessor,不是BeanFactoryPostProcessor,注意兩者的區別
*注意,這裡僅僅是註冊,並不會執行對應的方法,將在bean的例項化時執行對應的方法
*/
registerBeanPostProcessors(beanFactory);

/**
*初始化上下文中的資原始檔,如國際化檔案的處理等
*/
initMessageSource();

/**
*初始化上下文事件廣播器,並放入applicatioEventMulticaster,如ApplicationEventPublisher
*/
initApplicationEventMulticaster();

/**
*給子類擴充套件初始化其他Bean
*/
onRefresh();

/**
*在所有bean中查詢listenerbean,然後註冊到廣播器中
*/
registerListeners();

/**
*設定轉換器
*註冊一個預設的屬性值解析器
*凍結所有的bean定義,說明註冊的bean定義將不能被修改或進一步的處理
*初始化剩餘的非惰性的bean,即初始化非延遲載入的bean
*/
finishBeanFactoryInitialization(beanFactory);

/**
*通過spring的事件釋出機制釋出ContextRefreshedEvent事件,以保證對應的監聽器做進一步的處理
*即對那種在spring啟動後需要處理的一些類,這些類實現了ApplicationListener<ContextRefreshedEvent>,
*這裡就是要觸發這些類的執行(執行onApplicationEvent方法)
*另外,spring的內建Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
*完成初始化,通知生命週期處理器lifeCycleProcessor重新整理過程,同時發出ContextRefreshEvent通知其他人
*/
finishRefresh();
}

finally{

resetCommonCaches();
}
}
}

6. IOC容器的後置處理

一個擴充套件方法,原始碼如下:

afterRefresh(context,applicationArguments);

預設為空,如果有自定義需求可以重寫,比如列印一些啟動結束日誌等。

7. 發出結束執行的事件

同樣是EventPublishingRunListener這個監聽器,廣播ApplicationStartedEvent事件。

但是這裡廣播事件和前幾次不同,並不是廣播給SpringApplication中的監聽器(在構建過程中從spring.factories檔案獲取的監聽器)。因此在IOC容器中注入的監聽器(使用@Component等方式注入的)也能夠生效。前面幾個事件只有在spring.factories檔案中設定的監聽器才會生效。

跟著程式碼進入,可以看到started()方法原始碼如下:

這裡並沒有用事件廣播器SimpleApplicationEventMulticaster廣播事件,而是使用ConfigurableApplicationContext直接在IOC容器中釋出事件。

8. 執行Runners

Spring Boot提供了兩種Runner讓我們定製一些額外的操作,分別是CommandLineRunnerApplicationRunner,關於這兩個的區別,後面文章詳細介紹。

呼叫的原始碼如下:

callRunners(context,applicationArguments);

跟進程式碼,其實真正調執行的是如下方法:

邏輯很簡單,從IOC容器中獲取,遍歷呼叫。

總結

Spring Boot啟動流程相對簡單些,作者將其細分了以上八個步驟,希望能夠幫助讀者理解,流程圖如下:

總結

Spring Boot啟動流程就介紹到這裡了,需要重點理解run()方法執行的八個步驟以及事件、初始化器、監聽器等元件的執行時間點。