SpringBoot執(zhí)行原理
點擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達(dá)
? 作者?|??鄧曉暉
來源 |? urlify.cn/2YfYBf
一、執(zhí)行原理:
每個Spring Boot項目都有一個主程序啟動類,在主程序啟動類中有一個啟動項目的main()方法, 在該方法中通過執(zhí)行SpringApplication.run()即可啟動整個Spring Boot程序。
Q:
那么SpringApplication.run()方法到底是如何做到啟動Spring Boot項目的呢?
@SpringBootApplication??//能夠掃描Spring組件并自動配置SpringBoot
public?class?Springboot01DemoApplication?{
????public?static?void?main(String[]?args)?{
????????SpringApplication.run(Springboot01DemoApplication.class,?args);
????}
}
上述是一個SpringBoot的啟動類,進(jìn)入SpringApplication.run()方法

如圖所示,進(jìn)入了run方法后,緊接著,調(diào)用了重載方法,重載方法做了兩件事:
實例化SpringApplication對象
調(diào)用run方法
1. 實例化SpringApplication對象
public?SpringApplication(Class>...?primarySources)?{
?this(null,?primarySources);
}
public?SpringApplication(ResourceLoader?resourceLoader,?Class>...?primarySources)?{
?//......設(shè)置了一些參數(shù)....這里省略,下面是重點
????//......設(shè)置了一些參數(shù)....這里省略,下面是重點
????//......設(shè)置了一些參數(shù)....這里省略,下面是重點
?//項目啟動類?SpringbootDemoApplication.class設(shè)置為屬性存儲起來
?this.primarySources?=?new?LinkedHashSet<>(Arrays.asList(primarySources));
?//設(shè)置應(yīng)用類型是SERVLET應(yīng)用(Spring?5之前的傳統(tǒng)MVC應(yīng)用)還是REACTIVE應(yīng)用(Spring?5開始出現(xiàn)的WebFlux交互式應(yīng)用)
?this.webApplicationType?=?WebApplicationType.deduceFromClasspath();
?//?設(shè)置初始化器(Initializer),最后會調(diào)用這些初始化器
?//所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現(xiàn)類,在Spring上下文被刷新之前進(jìn)行初始化的操作
?setInitializers((Collection)?getSpringFactoriesInstances(ApplicationContextInitializer.class));
?//?設(shè)置監(jiān)聽器(Listener)
?setListeners((Collection)?getSpringFactoriesInstances(ApplicationListener.class));
?//?初始化?mainApplicationClass?屬性:用于推斷并設(shè)置項目main()方法啟動的主程序啟動類
?this.mainApplicationClass?=?deduceMainApplicationClass();
}
SpringApplication的構(gòu)造方法中,首先設(shè)置了一些參數(shù),然后做了5件事:
1.1 項目啟動類 SpringbootDemoApplication.class設(shè)置為屬性存儲起來
this.primarySources?=?new?LinkedHashSet<>(Arrays.asList(primarySources));
給這個成員變量賦值,把傳入的primarySources進(jìn)行轉(zhuǎn)換,然后賦值,這個primarySources就是我們Springboot啟動類的Main方法中傳入的:
1.2 設(shè)置應(yīng)用類型是SERVLET應(yīng)用(Spring 5之前的傳統(tǒng)MVC應(yīng)用)還是REACTIVE應(yīng)用
this.webApplicationType?=?WebApplicationType.deduceFromClasspath();
判斷當(dāng)前的web應(yīng)用類型是servlet應(yīng)用還是reactive應(yīng)用,那么如何判斷的?進(jìn)入.deduceFromClasspath()方法:
首先判斷類路徑下Reactive相關(guān)的class是否存在,如果存在就說明當(dāng)前應(yīng)用是內(nèi)嵌的 Reactive Web 應(yīng)用。例如說,Spring Webflux 。
判斷類路徑下是否存在Servlet類型的類。如果不存在,則返回
NONE,表示當(dāng)前應(yīng)用是非內(nèi)嵌的 Web 應(yīng)用。否則,表示當(dāng)前應(yīng)用是內(nèi)嵌的 Servlet Web 應(yīng)用。例如說,Spring MVC 。
1.3 設(shè)置初始化器(Initializer),最后會調(diào)用這些初始化器
所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現(xiàn)類,在Spring上下文被刷新之前進(jìn)行初始化的操作.
setInitializers((Collection)?getSpringFactoriesInstances(ApplicationContextInitializer.class));
這里傳入了一個ApplicationContextInitializer.class
進(jìn)入getSpringFactoriesInstances()方法(下圖如果看不清請右鍵另存為):
這段代碼主要做了如下幾件事:
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
這里的type就是剛才傳入的,ApplicationContextInitializer.classloadFactoryNames?調(diào)用了?loadSpringFactories方法loadSpringFactories方法做了如下的事:Enumerationurls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();判斷
classLoader是否為空,如果不為空加載META-INF下的spring.factories,如上圖所示,根據(jù)傳入的參數(shù)值(ApplicationContextInitializer.class)的類型,在spring.factories中進(jìn)行查找,根據(jù)當(dāng)前傳入的類型找到兩個類,這兩個類就是初始化器:org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
得到這兩個類后,把它們存入set去重,然后進(jìn)行實例化,然后排序,最終返回,到此初始化器已經(jīng)設(shè)置完成了。然后存入List,等待之后使用

1.4 設(shè)置監(jiān)聽器(Listener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
和1.3同理,也是通過調(diào)用getSpringFactoriesInstances,只不過傳遞的參數(shù)發(fā)生了改變。變成了ApplicationListener.class?,所以它就是在spring.factories中根據(jù)ApplicationListener.class找,然后實例化,然后返回存入Listeners中。
1.5 初始化 mainApplicationClass 屬性
用于推斷并設(shè)置項目main()方法啟動的主程序啟動類
this.mainApplicationClass?=?deduceMainApplicationClass();
?private?Class>?deduceMainApplicationClass()?{
??try?{
??????//?獲得當(dāng)前?StackTraceElement?數(shù)組
???StackTraceElement[]?stackTrace?=?new?RuntimeException().getStackTrace();
???//?判斷哪個執(zhí)行了?main?方法
???for?(StackTraceElement?stackTraceElement?:?stackTrace)?{
????if?("main".equals(stackTraceElement.getMethodName()))?{
?????return?Class.forName(stackTraceElement.getClassName());
????}
???}
??}?catch?(ClassNotFoundException?ex)?{
???//?Swallow?and?continue
??}
??return?null;
?}
判斷哪個類執(zhí)行了main方法,然后返回。
1.6 總結(jié)
實例化SpringApplication對象做了哪些事?
項目啟動類 SpringbootDemoApplication.class設(shè)置為屬性存儲起來
設(shè)置應(yīng)用類型是SERVLET應(yīng)用(Spring 5之前的傳統(tǒng)MVC應(yīng)用)還是REACTIVE應(yīng)用(Spring 5開始出現(xiàn)的WebFlux交互式應(yīng)用)
設(shè)置初始化器(Initializer),最后會調(diào)用這些初始化器
設(shè)置監(jiān)聽器(Listener)
初始化 mainApplicationClass 屬性:用于推斷并設(shè)置項目main()方法啟動的主程序啟動類
2. 調(diào)用run方法
回憶一下,在SpringBoot啟動類的Main方法中,執(zhí)行了SpringApplication.run(Main方法所在的當(dāng)前類.class, args);,這個方法主要做了兩件事:
實例化SpringApplication對象 (已上述)
調(diào)用run方法
進(jìn)入run方法:
run方法大體上做了9件比較重要的事。
2.1 獲取并啟動監(jiān)聽器
SpringApplicationRunListeners?listeners?=?getRunListeners(args);
listeners.starting();
//args是啟動Spring應(yīng)用的命令行參數(shù),該參數(shù)可以在Spring應(yīng)用中被訪問。如:--server.port=9000
ApplicationArguments?applicationArguments?=?new?DefaultApplicationArguments(args);

它其實還是通過getSpringFactoriesInstances()這個方法來獲取,這個方法已經(jīng)很熟悉了, 1.3,1.4都使用到了,不再贅述。
那么本步驟就是通過getSpringFactoriesInstances()拿到了一個SpringApplicationRunListeners類型的監(jiān)聽器,然后調(diào)用.starting()啟動。
2.2 項目運(yùn)行環(huán)境Environment的預(yù)配置
創(chuàng)建并配置當(dāng)前SpringBoot應(yīng)用將要使用的Environment,并遍歷調(diào)用所有的SpringApplicationRunListener的environmentPrepared()方法
ConfigurableEnvironment?environment?=?prepareEnvironment(listeners,?applicationArguments);
configureIgnoreBeanInfo(environment);
//?準(zhǔn)備Banner打印器?-?就是啟動Spring?Boot的時候打印在console上的ASCII藝術(shù)字體
Banner?printedBanner?=?printBanner(environment);
進(jìn)入prepareEnvironment()方法:
查詢environment,有就返回,沒有的話創(chuàng)建后返回。
配置環(huán)境
PropertySources:加載執(zhí)行的配置文件
Profiles:多環(huán)境配置,針對不同的環(huán)境,加載不同的配置
listeners環(huán)境準(zhǔn)備(就是廣播ApplicationEnvironmentPreparedEvent事件)。
將創(chuàng)建的環(huán)境綁定到SpringApplication對象上
是否是web環(huán)境,如果不是就轉(zhuǎn)換為標(biāo)準(zhǔn)環(huán)境
配置PropertySources對它自己的遞歸依賴
返回
此時已經(jīng)拿到了ConfigurableEnvironment?環(huán)境對象,然后執(zhí)行configureIgnoreBeanInfo(environment),使其生效。
2.3 創(chuàng)建Spring容器
context?=?createApplicationContext();
//?獲得異常報告器?SpringBootExceptionReporter?數(shù)組
//這一步的邏輯和實例化初始化器和監(jiān)聽器的一樣,
//?都是通過調(diào)用 getSpringFactoriesInstances 方法來獲取配置的異常類名稱并實例化所有的異常處理類。
exceptionReporters?=?getSpringFactoriesInstances(
??????SpringBootExceptionReporter.class,
??????new?Class[]?{?ConfigurableApplicationContext.class?},?context);

根據(jù)?webApplicationType?類型,獲得?ApplicationContext?類型,這里創(chuàng)建容器的類型 還是根據(jù)webApplicationType進(jìn)行判斷的,該類型為SERVLET類型,所以會通過反射裝載對應(yīng)的字節(jié)碼,也就是AnnotationConfigServletWebServerApplicationContext。
然后通過getSpringFactoriesInstances()獲得異常報告器。
2.4 Spring容器前置處理
這一步主要是在容器刷新之前的準(zhǔn)備動作。包含一個非常關(guān)鍵的操作:將啟動類注入容器,為后續(xù)開啟自動化配置奠定基礎(chǔ)。
prepareContext(context,?environment,?listeners,?applicationArguments,
??????printedBanner);

這塊會對整個上下文進(jìn)行一個預(yù)處理,比如觸發(fā)監(jiān)聽器的響應(yīng)事件、加載資源、設(shè)置上下文環(huán)境等等。
2.5 刷新容器
refreshContext(context);

IOC容器初始化
向JVM運(yùn)行時注冊一個關(guān)機(jī)鉤子(函數(shù)),在JVM關(guān)機(jī)時關(guān)閉這個上下文,除非它當(dāng)時已經(jīng)關(guān)閉。(如果jvm變關(guān)閉了,當(dāng)前上下文對象也可以被關(guān)閉了)
//TODO refresh方法在springioc章節(jié)中會有詳細(xì)說明(挖個坑- - )。
2.6 Spring容器后置處理
afterRefresh(context, applicationArguments);

擴(kuò)展接口,設(shè)計模式中的模板方法,默認(rèn)為空實現(xiàn)。
如果有自定義需求,可以重寫該方法。比如打印一些啟動結(jié)束log,或者一些其它后置處理。
2.7 發(fā)出結(jié)束執(zhí)行的事件通知
listeners.started(context);

2.8 執(zhí)行Runners運(yùn)行器
callRunners(context, applicationArguments);

用于調(diào)用項目中自定義的執(zhí)行器XxxRunner類,使得在項目啟動完成后立即執(zhí)行一些特定程序。
Runner 運(yùn)行器用于在服務(wù)啟動時進(jìn)行一些業(yè)務(wù)初始化操作,這些操作只在服務(wù)啟動后執(zhí)行一次。
Spring Boot提供了ApplicationRunner和CommandLineRunner兩種服務(wù)接口
2.9 發(fā)布應(yīng)用上下文就緒事件
listeners.running(context);
表示在前面一切初始化啟動都沒有問題的情況下,使用運(yùn)行監(jiān)聽器SpringApplicationRunListener持續(xù)運(yùn)行配置好的應(yīng)用上下文ApplicationContext.
這樣整個Spring Boot項目就正式啟動完成了。
2.10 返回容器
return context;
完成~
總結(jié):
獲取并啟動監(jiān)聽器
項目運(yùn)行環(huán)境Environment的預(yù)配置
創(chuàng)建Spring容器
Spring容器前置處理
刷新容器
Spring容器后置處理
發(fā)出結(jié)束執(zhí)行的事件通知
執(zhí)行Runners運(yùn)行器
發(fā)布應(yīng)用上下文就緒事件
返回容器
粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
???

?長按上方微信二維碼?2 秒
感謝點贊支持下哈?
