Spring boot 2.5.x 啟動流程源碼分析

我想知道為什么 spring boot 啟動就一行代碼,直接完成了那么多功能,今天就進(jìn)去看個究竟,順便記錄一下。
第一個 run 方法
要想往進(jìn)去看,入口少不了,就是你了,這看著人畜無害的一行代碼,直接點進(jìn)去。

然后便來到了這里,根據(jù)入?yún)⒅苯涌催@個 run() ,發(fā)現(xiàn)這個 run() 里就 new 了這個 SpringApplication(primarySources) 的匿名對象,然后執(zhí)行了 run。

new SpringApplication()
那么就先來看看這個 SpringApplication 的構(gòu)造方法吧。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//用LinkedHashSet將主類給保存了起來
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//對項目的類型做一個判斷,WebApplicationType枚舉類中有三種,后邊這個方法就是一個判斷方法
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//這里將對象注冊表初始化,然后保存起來
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
//這個是要通過 SPI 機(jī)制去掃描 META-INF/spring.factories ,加載 ApplicationContextInitializer 接口的實例,這個類當(dāng)springboot上下文Context初始化完成后會調(diào)用
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//同上,這個 ApplicationListener 類當(dāng)springboot啟動時事件change后都會觸發(fā)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//堆棧跟蹤判斷方法是否是 main
this.mainApplicationClass = deduceMainApplicationClass();
}簡單說一下 SPI 機(jī)制
我對此機(jī)制的概念理解:提供給服務(wù)提供廠商與擴(kuò)展框架功能的開發(fā)者使用的接口,約定如下:
在META-INF/services/目錄中創(chuàng)建以接口全限定名命名的文件該文件內(nèi)容為Api具體實現(xiàn)類的全限定名
使用ServiceLoader類動態(tài)加載META-INF中的實現(xiàn)類
如SPI的實現(xiàn)類為Jar則需要放在主程序classPath中
Api具體實現(xiàn)類必須有一個不帶參數(shù)的構(gòu)造方法
判斷項目類型的那個方法

對象注冊表初始化那個方法
這里可以再往進(jìn)去看看。

看看這個類 Bootstrapper.class,上邊 lambda 表達(dá)式里用的接口和方法就是這里,再可以看看這個入?yún)⑹莻€啥這個 BootstrapRegistry 類。

可以發(fā)現(xiàn),這個類里出現(xiàn)了熟悉的東西,單例和原的枚舉值。

如何使用 SPI 機(jī)制掃描 META-INF/spring.factories 這個文件,并且加載接口實例
這里我們可以自己實現(xiàn)一下 ApplicationContextInitializer.class 和 ApplicationListener.class 這倆個接口的實現(xiàn)類。


將這兩個實現(xiàn)類寫出來以后,然后在 resourse 目錄下寫一個 META-INF/spring.factories ,如上圖所示,然后配置以下自己實現(xiàn)的那兩個類,如下:
org.springframework.context.ApplicationContextInitializer=\
hasaki_w_c.demo.listener.MyApplicationContextInitializer
org.springframework.context.ApplicationListener=\
hasaki_w_c.demo.listener.MyApplicationListener這樣,就把我們自己寫的接口實現(xiàn)類集成到 spring boot 中去了,然后從 new SpringApplication() 那一段構(gòu)造代碼中的 getSpringFactoriesInstances() 這個方法進(jìn)去

然后再進(jìn)到這個方法里邊,入?yún)鬟M(jìn)來的那兩個類都會走這里的這兩個方法,所以我們直接在這個 loadSpringFactories 這個方法里 debug

然后打斷點,看一下是不是添加進(jìn)去了,斷點的地方就打在返回值里

然后調(diào)試啟動這個項目,然后我這里把 result 的關(guān)于自己實現(xiàn)的那倆個接口拿出來監(jiān)視了,查看其里邊的值,會發(fā)現(xiàn),自己實現(xiàn)的那兩個接口也被添加了進(jìn)去。

然后從控制臺也可以直觀的看到,自己實現(xiàn)的兩個接口和方法都被打印了出來。

然后稍微詳細(xì)地看了一下 loadSpringFactories 這個方法,大概就是先使用 classLoader.getResources() 將 spring.factories 中的實現(xiàn)類加載到Enumeration 接口中,接口如下:

這個接口是有兩個方法,一個判斷里邊還有沒有元素了,另一個是返回下一個元素。
然后通過兩三次的類型轉(zhuǎn)換,轉(zhuǎn)換成 Properties 對象(Properties 類繼承了Hashtable),然后遍歷,取到這個properties 的 key,然后通過 StringUtils 里邊有個把逗號分隔的列表直接轉(zhuǎn)化成 String 數(shù)組的那個方法獲取到 properties 的 value,然后將這些key和value加入到返回值中,最后用包含唯一元素的不可修改列表替換所有列表,最后返回這個Map類型的返回值;這里就完成了這兩個接口實例的設(shè)置。
堆棧跟蹤判斷
new SpringApplication() 的最后一行,就是將堆棧跟蹤返回值保存,堆棧跟蹤方法中通過判斷每一個元素的方法是不是equals “main”,如果是則通過Class.forName 返回這個元素的 class 對象,不是則返回 null。

到這里這個 SpringApplication 的方法就看完了。
第二個 run 方法
有興趣的可以都點進(jìn)去看一看,我這里就不一一截圖了。
public ConfigurableApplicationContext run(String... args) {
//啟動一個 StopWatch 計時器(spring 5.2以后,運行時間就以納秒進(jìn)行跟蹤了)
StopWatch stopWatch = new StopWatch();
//計時器啟動
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
//設(shè)置環(huán)境變量
configureHeadlessProperty();
//獲取事件監(jiān)聽器SpringApplicationRunListener類型
SpringApplicationRunListeners listeners = getRunListeners(args);
//入?yún)⑹巧舷挛膶ο蠛颓斑卬ew SpringApplication最后那個堆棧跟蹤的返回值,這里就啟動了監(jiān)聽器
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//然后把參數(shù)封裝成提供運行SpringApplication 的參數(shù)的訪問的這個接口對象,通過這個接口的默認(rèn)實現(xiàn)類進(jìn)行的
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//然后把環(huán)境和上下文以及上邊它剛剛封裝好的那個參數(shù)訪問對象綁定
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
//對環(huán)境的值判斷,以及設(shè)置
configureIgnoreBeanInfo(environment);
//然后打印 banner (就是那個啟動后的控制臺比較大的那個spring boot)(終于直到那個圖形哪里來的了)
Banner printedBanner = printBanner(environment);
//創(chuàng)建上下文,根據(jù)項目類型創(chuàng)建上下文,這個創(chuàng)建的方法里是要用到new 時候那個 WebApplicationType
context = createApplicationContext();
//保存使用ApplicationStartup來標(biāo)記應(yīng)用程序啟動期間的步驟,以及記錄一些收集到的執(zhí)行上下文或其處理時間的數(shù)據(jù)
context.setApplicationStartup(this.applicationStartup);
//準(zhǔn)備上下文,將前邊的幾個變量全丟進(jìn)這個方法的參數(shù)里
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//這個就是非常重要的東西了,是spring 的啟動代碼,這里邊掃描并初始化 bean 了,然后 spring boot 內(nèi)嵌的web 容器也在這里邊被啟動,需要進(jìn)去細(xì)看了。
refreshContext(context);
//進(jìn)去看了以下發(fā)現(xiàn)啥也沒干
afterRefresh(context, applicationArguments);
//停止計時器
stopWatch.stop();
if (this.logStartupInfo) {
//啟動時記錄程序的信息,并打印日志
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//執(zhí)行ApplicationRunListeners中的started()方法
listeners.started(context);
//執(zhí)行兩個Runner(ApplicationRunner和CommandLineRunner)
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//處理運行失敗
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
//執(zhí)行ApplicationRunListeners中的started()方法
listeners.running(context);
}
catch (Throwable ex) {
//處理運行失敗
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}refreshContext() 方法
點進(jìn)去看看

發(fā)現(xiàn)有一個 refresh 方法,再點進(jìn)去看看

然后又是一個 refresh 方法,再干進(jìn)去

找到這個方法的實現(xiàn),是一個抽象類里邊的實現(xiàn)方法,發(fā)現(xiàn)這里就是 spring 包下的類了,這個就是 spring 的啟動類了,容器就在這里初始化,每一行代碼也都有注釋,代碼附上

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//這個重點看一下
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}大概就是有這么些功能:
設(shè)置上下文狀態(tài),獲取一些屬性,獲取并配置新的BeanFactory,實例化并調(diào)用所有注冊的beanFactory后置處理器,實例化和注冊beanFactory中擴(kuò)展了被注解修飾了的那些類的bean,初始化事件廣播器,這里重點看一下onRefresh()這個方法,找到這個重寫的方法,然后再繼續(xù)往里看,這時候就可以發(fā)現(xiàn)有個getWebServer()的接口方法了,然后它的實現(xiàn)類就是tomcat、jetty了,內(nèi)嵌的 tomcat 也啟動了,然后注冊監(jiān)聽器,實例化所有剩余的(非懶加載)單例,最后finishRefresh,清除上下文資源緩存,初始化上下文的生命周期處理器。
onRefresh() 方法
這里看一下這個方法,從 spring 的 refresh() 中點進(jìn)去,是抽象類的方法,然后找到它的重寫方法,這里由于我們一般是 web 工程,所以找 springboot 包下的這個方法

然后再進(jìn)去這個 createWebServer ,它的名字告訴我 web 容器就從這里初始化的,直接點進(jìn)去看看

先看一下這個 getWebServerFactory() 方法,這個方法可以直到選擇了哪種類型的 web 容器,點進(jìn)去在下圖地方打個斷點調(diào)試一下看看,可以看到熟悉的tomcat 了,這時候回到上邊 createWebServer() ,可以看到后邊這個工廠調(diào)用了 getWebServer() 這個方法。

點進(jìn)去這個 getWebServer() ,查看它的實現(xiàn)類,這里有三個實現(xiàn)類,

終于發(fā)現(xiàn) web 容器在哪初始化的了,看一下 tomcat 的,明顯看出是在初始化設(shè)置參數(shù)的值。

這里 web 容器就成功啟動了,然后再就是執(zhí)行 onRefresh() 方法之后的那幾個方法了,再一步一步倒回去看看,好像大概的流程都看完了,從最初的 run,到最后跑到 spring 里,回去在再把第二個 run 跑完,整個 spring boot 的啟動就完成了。
此次對 spring boot 框架的源碼學(xué)習(xí),讓我對它如何啟動有了一個較為清晰的認(rèn)識,雖還可能有遺漏,但大概流程應(yīng)該沒跑偏。
出處:blog.csdn.net/qq_45456859/article/details/120142517
關(guān)注GitHub今日熱榜,專注挖掘好用的開發(fā)工具,致力于分享優(yōu)質(zhì)高效的工具、資源、插件等,助力開發(fā)者成長!
點個在看,你最好看
