Spring boot 2.5.x 啟動(dòng)流程源碼分析

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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.
//這個(gè)重點(diǎn)看一下
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,實(shí)例化并調(diào)用所有注冊(cè)的beanFactory后置處理器,實(shí)例化和注冊(cè)beanFactory中擴(kuò)展了被注解修飾了的那些類(lèi)的bean,初始化事件廣播器,這里重點(diǎn)看一下onRefresh()這個(gè)方法,找到這個(gè)重寫(xiě)的方法,然后再繼續(xù)往里看,這時(shí)候就可以發(fā)現(xiàn)有個(gè)getWebServer()的接口方法了,然后它的實(shí)現(xiàn)類(lèi)就是tomcat、jetty了,內(nèi)嵌的 tomcat 也啟動(dòng)了,然后注冊(cè)監(jiān)聽(tīng)器,實(shí)例化所有剩余的(非懶加載)單例,最后finishRefresh,清除上下文資源緩存,初始化上下文的生命周期處理器。
onRefresh() 方法
這里看一下這個(gè)方法,從 spring 的 refresh() 中點(diǎn)進(jìn)去,是抽象類(lèi)的方法,然后找到它的重寫(xiě)方法,這里由于我們一般是 web 工程,所以找 springboot 包下的這個(gè)方法

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

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

點(diǎn)進(jìn)去這個(gè) getWebServer() ,查看它的實(shí)現(xiàn)類(lèi),這里有三個(gè)實(shí)現(xiàn)類(lèi),

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

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