SpringBoot整個啟動過程的分析
前言
前一篇分析了SpringBoot如何啟動以及內(nèi)置web容器,這篇我們一起看一下SpringBoot的整個啟動過程,廢話不多說,正文開始。
正文
一、SpringBoot的啟動類是**application,以注解@SpringBootApplication注明。
@SpringBootApplication
public class CmsApplication {
public static void main(String[] args) {
SpringApplication.run(CmsApplication.class, args);
}
}
SpringBootApplication注解是@Configuration,@EnableAutoConfiguration,@ComponentScan三個注解的集成,分別表示Springbean的配置bean,開啟自動配置spring的上下文,組件掃描的路徑,這也是為什么*application.java需要放在根路徑的原因,這樣@ComponentScan掃描的才是整個項目。
二、該啟動類默認(rèn)只有一個main方法,調(diào)用的是SpringApplication.run方法,下面我們來看一下SpringApplication這個類。
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[]{source}, args);
}
...
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return (new SpringApplication(sources)).run(args);//sources為具體的CmsApplication.class類
}
...
抽出其中兩個直接調(diào)用的run方法,可以看出靜態(tài)方法SpringApplication.run最終創(chuàng)建了一個SpringApplication,并運(yùn)行其中run方法。
查看起構(gòu)造方法:
public SpringApplication(Object... sources) {
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.initialize(sources);
}
...
構(gòu)造方法設(shè)置了基礎(chǔ)值后調(diào)用initialize方法進(jìn)行初始化,如下:
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = this.deduceWebEnvironment();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
...
初始化方法主要做了幾步:
1.將source放入SpringApplication的sources屬性中管理,sources是一個LinkedHashSet(),這意味著我們可以同時創(chuàng)建多個自定義不重復(fù)的Application,但是目前只有一個。
2.判斷是否是web程序(javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext都必須在類加載器中存在),并設(shè)置到webEnvironment屬性中。
3.從spring.factories中找出ApplicationContextInitializer并設(shè)置到初始化器initializers。
4.從spring.factories中找出ApplicationListener,并實例化后設(shè)置到SpringApplication的監(jiān)聽器listeners屬性中。這個過程就是找出所有的應(yīng)用程序事件監(jiān)聽器。
5.找出的main方法的類(這里是CmsApplication),并返回Class對象。
默認(rèn)情況下,initialize方法從spring.factories文件中找出的key為ApplicationContextInitializer的類有:
- org.springframework.boot.context.config.DelegatingApplicationContextInitializer
- org.springframework.boot.context.ContextIdApplicationContextInitializer
- org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
- org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
- org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
key為ApplicationListener的有:
- org.springframework.boot.context.config.ConfigFileApplicationListener
- org.springframework.boot.context.config.AnsiOutputApplicationListener
- org.springframework.boot.logging.LoggingApplicationListener
- org.springframework.boot.logging.ClasspathLoggingApplicationListener
- org.springframework.boot.autoconfigure.BackgroundPreinitializer
- org.springframework.boot.context.config.DelegatingApplicationListener
- org.springframework.boot.builder.ParentContextCloserApplicationListener
- org.springframework.boot.context.FileEncodingApplicationListener
- org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
三、SpringApplication構(gòu)造和初始化完成后,便是運(yùn)行其run方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();// 構(gòu)造一個任務(wù)執(zhí)行觀察器
stopWatch.start();// 開始執(zhí)行,記錄開始時間
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
this.configureHeadlessProperty();
// 獲取SpringApplicationRunListeners,內(nèi)部只有一個EventPublishingRunListener
SpringApplicationRunListeners listeners = this.getRunListeners(args);
// 封裝成SpringApplicationEvent事件然后廣播出去給SpringApplication中的listeners所監(jiān)聽,啟動監(jiān)聽
listeners.starting();
try {
// 構(gòu)造一個應(yīng)用程序參數(shù)持有類
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 加載配置環(huán)境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
Banner printedBanner = this.printBanner(environment);
// 創(chuàng)建Spring容器(使用BeanUtils.instantiate)
context = this.createApplicationContext();
// 若容器創(chuàng)建失敗,分析輸出失敗原因
new FailureAnalyzers(context);
// 設(shè)置容器配置環(huán)境,監(jiān)聽等
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新容器
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
// 廣播出ApplicationReadyEvent事件給相應(yīng)的監(jiān)聽器執(zhí)行
listeners.finished(context, (Throwable)null);
stopWatch.stop();// 執(zhí)行結(jié)束,記錄執(zhí)行時間
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
return context;// 返回Spring容器
} catch (Throwable var9) {
this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
throw new IllegalStateException(var9);
}
}
run方法過程分析如上,該方法幾個關(guān)鍵步驟如下:
1.創(chuàng)建了應(yīng)用的監(jiān)聽器SpringApplicationRunListeners并開始監(jiān)聽
2.加載SpringBoot配置環(huán)境(ConfigurableEnvironment),如果是通過web容器發(fā)布,會加載StandardEnvironment,其最終也是繼承了ConfigurableEnvironment,類圖如下

可以看出,*Environment最終都實現(xiàn)了PropertyResolver接口,我們平時通過environment對象獲取配置文件中指定Key對應(yīng)的value方法時,就是調(diào)用了propertyResolver接口的getProperty方法。
3.配置環(huán)境(Environment)加入到監(jiān)聽器對象中(SpringApplicationRunListeners)
4.創(chuàng)建Spring容器:ConfigurableApplicationContext(應(yīng)用配置上下文),我們可以看一下創(chuàng)建方法
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment ? "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext" : "org.springframework.context.annotation.AnnotationConfigApplicationContext");
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass);
}
方法會先獲取顯式設(shè)置的應(yīng)用上下文(applicationContextClass),如果不存在,再加載默認(rèn)的環(huán)境配置(通過是否是web environment判斷),默認(rèn)選擇AnnotationConfigApplicationContext注解上下文(通過掃描所有注解類來加載bean),最后通過BeanUtils實例化上下文對象,并返回,ConfigurableApplicationContext類圖如下

主要看其繼承的兩個方向:
- LifeCycle:生命周期類,定義了start啟動、stop結(jié)束、isRunning是否運(yùn)行中等生命周期空值方法
- ApplicationContext:應(yīng)用上下文類,其主要繼承了beanFactory(bean的工廠類)。
5.回到run方法內(nèi),設(shè)置容器prepareContext方法,將listeners、environment、applicationArguments、banner等重要組件與上下文對象關(guān)聯(lián)
6.刷新容器,refresh()方法,初始化方法如下:
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
refresh()方法做了很多核心工作比如BeanFactory的設(shè)置,BeanFactoryPostProcessor接口的執(zhí)行、BeanPostProcessor接口的執(zhí)行、自動化配置類的解析、spring.factories的加載、bean的實例化、條件注解的解析、國際化的初始化等等。這部分內(nèi)容會在之后的文章中分析。
7.廣播出ApplicationReadyEvent,執(zhí)行結(jié)束返回ConfigurableApplicationContext。
至此,SpringBoot啟動完成,回顧整體流程,Springboot的啟動,主要創(chuàng)建了配置環(huán)境(environment)、事件監(jiān)聽(listeners)、應(yīng)用上下文(applicationContext),并基于以上條件,在容器中開始實例化我們需要的Bean。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請查看下面相關(guān)鏈接
相關(guān)文章
Java實現(xiàn)日志文件監(jiān)聽并讀取相關(guān)數(shù)據(jù)的方法實踐
本文主要介紹了Java實現(xiàn)日志文件監(jiān)聽并讀取相關(guān)數(shù)據(jù)的方法實踐,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05
java使用動態(tài)代理來實現(xiàn)AOP(日志記錄)的實例代碼
AOP(面向方面)的思想,就是把項目共同的那部分功能分離開來,比如日志記錄,避免在業(yè)務(wù)邏輯里面夾雜著跟業(yè)務(wù)邏輯無關(guān)的代碼2013-09-09
Spring核心容器之ApplicationContext上下文啟動準(zhǔn)備詳解
這篇文章主要介紹了Spring核心容器之ApplicationContext上下文啟動準(zhǔn)備詳解,ApplicationContext 繼承自 BeanFactory ,其不僅包含 BeanFactory 所有功能,還擴(kuò)展了容器功能,需要的朋友可以參考下2023-11-11
Java構(gòu)造器(構(gòu)造方法)能否可以被重寫
這篇文章主要介紹了Java構(gòu)造器(構(gòu)造方法)能否可以被重寫問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03

