環(huán)境:
- spring boot 2.0.8.release
- win10
- idea 2018.03.05
本文查看相關(guān)的文章與源代碼總結(jié)的码泛。
1. spring boot 啟動(dòng)流程
查看 SpringBoot啟動(dòng)流程解析 這篇文章窖铡,那里的啟動(dòng)結(jié)構(gòu)圖還是不錯(cuò)的。
啟動(dòng)流程主要分為三部分盈电,
- 第一部分, 進(jìn)行SpringApplication的初始化模塊蓄拣,配置一些環(huán)境變量辽故、資源、構(gòu)造器风宁、監(jiān)聽器洁墙、主方法的類;
- 第二部分戒财,實(shí)現(xiàn)應(yīng)用的啟動(dòng)热监,包括啟動(dòng)流程的監(jiān)聽模塊、加載配置環(huán)境模塊饮寞、核心的創(chuàng)建上下文環(huán)境模塊孝扛;
- 第三部分,自動(dòng)化配置模塊幽崩,該模塊作為spring boot的自動(dòng)配置核心苦始。
查看一下的圖,這個(gè)圖比較精彩慌申。
如果不夠全面陌选,就查看[SpringBoot啟動(dòng)結(jié)構(gòu)圖] (https://www.processon.com/view/link/59812124e4b0de2518b32b6e)
2. 啟動(dòng)
每個(gè)SpringBoot程序都有一個(gè)主入口,也就是main方法太示,main里面調(diào)用SpringApplication.run()啟動(dòng)整個(gè)spring-boot程序柠贤,該方法所在類需要使用@SpringBootApplication注解,以及@ImportResource注解(if need)类缤,@SpringBootApplication包括三個(gè)注解臼勉,功能如下:
@EnableAutoConfiguration:SpringBoot根據(jù)應(yīng)用所聲明的依賴來對(duì)Spring框架進(jìn)行自動(dòng)配置
@SpringBootConfiguration(內(nèi)部為@Configuration):被標(biāo)注的類等于在spring的XML配置文件中(applicationContext.xml),裝配所有bean事務(wù)餐弱,提供了一個(gè)spring的上下文環(huán)境
@ComponentScan:組件掃描宴霸,可自動(dòng)發(fā)現(xiàn)和裝配Bean囱晴,默認(rèn)掃描SpringApplication的run方法里的Demo.class所在的包路徑下文件,所以最好將該啟動(dòng)類放到根包路徑下
3. 初始化模塊
SpringBoot啟動(dòng)類進(jìn)入run()方法瓢谢,首先會(huì)創(chuàng)建SpringApplication實(shí)例畸写,然后在構(gòu)造函數(shù)內(nèi),初始化initialize.
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
------------------------------------------
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
------------------------------------------
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
-------------------------------------------
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 配置類加載器
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 配置sources
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 判斷應(yīng)用的類型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 創(chuàng)建初始化構(gòu)造器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));氓扛、
// 配置監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 配置應(yīng)用主類
this.mainApplicationClass = deduceMainApplicationClass();
}
其中WebApplicationType.deduceFromClasspath();主要配置是否為Web環(huán)境枯芬, REACTIVE環(huán)境,None(單jar)采郎。
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
注意: classUtils.isPresent(className, null)判斷所提供的類名的類是否存在千所,且可以被加載
java.lang.Class.forName()的作用是什么?
返回與給定字符串名的類或接口的Class對(duì)象蒜埋,使用給定的類加載器淫痰。
private static final String WEBFLUX_INDICATOR_CLASS = "org."
+ "springframework.web.reactive.DispatcherHandler";
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
4. 應(yīng)用的啟動(dòng)
應(yīng)用的啟動(dòng),會(huì)啟動(dòng)監(jiān)聽器整份、配置環(huán)境模塊待错、配置Banner、配置應(yīng)用上下文模塊
public ConfigurableApplicationContext run(String... args) {
// 開啟計(jì)時(shí)器開始計(jì)時(shí)
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// headless模式配置
configureHeadlessProperty();
// 配置監(jiān)聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 配置環(huán)境變量
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 配置Banner
Banner printedBanner = printBanner(environment);
// 創(chuàng)建應(yīng)用上下文
context = createApplicationContext();
// 獲取springboot 異常類
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 更新應(yīng)用上下文烈评,prepareContext方法將listeners火俄、environment、applicationArguments础倍、banner等重要組件與上下文對(duì)象關(guān)聯(lián)
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// (初始化方法如下)將是實(shí)現(xiàn)spring-boot-starter-*(mybatis烛占、redis等)自動(dòng)化配置的關(guān)鍵,包括spring.factories的加載沟启,bean的實(shí)例化等核心工作忆家。
refreshContext(context);
// Springboot做了一些基本的收尾工作,返回了應(yīng)用環(huán)境上下文
afterRefresh(context, applicationArguments);
// 停止計(jì)時(shí)
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
// 這里會(huì)檢測是否Runner德迹,在項(xiàng)目啟動(dòng)后運(yùn)行的芽卿,一般是CommandRunner、ApplicationRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
該方法中實(shí)現(xiàn)了如下幾個(gè)關(guān)鍵步驟:
- 創(chuàng)建了應(yīng)用的監(jiān)聽器SpringApplicationRunListeners并開始監(jiān)聽
- 加載SpringBoot配置環(huán)境(ConfigurableEnvironment)胳搞,如果是通過web容器發(fā)布卸例,會(huì)加載StandardEnvironment,其最終也是繼承了ConfigurableEnvironment肌毅,類圖如下
可以看出筷转,*Environment最終都實(shí)現(xiàn)了PropertyResolver接口,我們平時(shí)通過environment對(duì)象獲取配置文件中指定Key對(duì)應(yīng)的value方法時(shí)悬而,就是調(diào)用了propertyResolver接口的getProperty方法
- 配置環(huán)境(Environment)加入到監(jiān)聽器對(duì)象中(SpringApplicationRunListeners)
- 創(chuàng)建run方法的返回對(duì)象:ConfigurableApplicationContext(應(yīng)用配置上下文)呜舒,我們可以看一下創(chuàng)建方法:
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
方法會(huì)先獲取顯式設(shè)置的應(yīng)用上下文(applicationContextClass),如果不存在笨奠,再加載默認(rèn)的環(huán)境配置(通過是否是web environment判斷)袭蝗,默認(rèn)選擇AnnotationConfigApplicationContext注解上下文(通過掃描所有注解類來加載bean)唤殴,最后通過BeanUtils實(shí)例化上下文對(duì)象,并返回到腥,ConfigurableApplicationContext類圖如下:
主要看其繼承的兩個(gè)方向:
LifeCycle:生命周期類朵逝,定義了start啟動(dòng)、stop結(jié)束乡范、isRunning是否運(yùn)行中等生命周期空值方法
ApplicationContext:應(yīng)用上下文類配名,其主要繼承了beanFactory(bean的工廠類)
5.回到run方法內(nèi),prepareContext方法將listeners篓足、environment段誊、applicationArguments闰蚕、banner等重要組件與上下文對(duì)象關(guān)聯(lián)
6.接下來的refreshContext(context)方法(初始化方法如下)將是實(shí)現(xiàn)spring-boot-starter-*(mybatis栈拖、redis等)自動(dòng)化配置的關(guān)鍵,包括spring.factories的加載没陡,bean的實(shí)例化等核心工作涩哟。
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
// 注冊(cè)關(guān)閉的鉤子函數(shù),用于收尾工作
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
-----------------------
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
-----------------------
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
// 刷新盼玄,檢測環(huán)境變量
this.prepareRefresh();
// 獲取ConfigurableListableBeanFactory工廠
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方法
配置結(jié)束后贴彼,Springboot做了一些基本的收尾工作,返回了應(yīng)用環(huán)境上下文埃儿。
回顧整體流程器仗,Springboot的啟動(dòng),主要?jiǎng)?chuàng)建了配置環(huán)境(environment)童番、事件監(jiān)聽(listeners)精钮、應(yīng)用上下文(applicationContext),并基于以上條件剃斧,在容器中開始實(shí)例化我們需要的Bean轨香,至此,通過SpringBoot啟動(dòng)的程序已經(jīng)構(gòu)造完成幼东,接下來我們來探討自動(dòng)化配置是如何實(shí)現(xiàn)臂容。
引用:
PS: 若你覺得可以、還行根蟹、過得去脓杉、甚至不太差的話,可以“關(guān)注”一下简逮,就此謝過!