目錄
一、前言
二沛豌、SpringBoot啟動流程梳理
三庸娱、第一步:獲取并啟動監(jiān)聽器
四链峭、第二步:構(gòu)造應(yīng)用上下文環(huán)境
4.1畦娄、 ConfigurableEnvironment environment = getOrCreateEnvironment();
4.2、 configureEnvironment(environment, applicationArguments.getSourceArgs());
4.3熏版、 listeners.environmentPrepared(environment);
五纷责、第三步:初始化應(yīng)用上下文
一捍掺、前言
前一篇介紹了 SpringApplication 類的實例化過程撼短,本章總結(jié)SpringBoot啟動流程最重要的部分run方法。
通過run方法梳理出SpringBoot啟動的流程挺勿,然后后面的博客再一步步的分析啟動流程中各個步驟所做的具體的工作曲横。深入分析后會發(fā)現(xiàn)SpringBoot也就是給Spring包了一層皮,事先替我們準(zhǔn)備好Spring所需要的環(huán)境及一些基礎(chǔ),具體通過源碼一步步深入分析后會發(fā)現(xiàn)Spring是真的很偉大禾嫉。當(dāng)然跟代碼的時候越深入越容易陷進(jìn)去進(jìn)而發(fā)現(xiàn)有些東西沒法通過博客詳細(xì)的梳理出來灾杰。當(dāng)然在這個過程中還是立足于我們對SpringBoot的使用來說明源碼所做的工作。知其然才能知其所以然熙参。加油
二艳吠、SpringBoot啟動流程梳理
首先擺上run方法的源碼
public ConfigurableApplicationContext run(String... args) {
//記錄程序運行時間
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// ConfigurableApplicationContext Spring 的上下文
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
//從META-INF/spring.factories中獲取監(jiān)聽器
//1、獲取并啟動監(jiān)聽器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//2孽椰、構(gòu)造應(yīng)用上下文環(huán)境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
//處理需要忽略的Bean
this.configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = this.printBanner(environment);
//3昭娩、初始化應(yīng)用上下文
context = this.createApplicationContext();
//實例化SpringBootExceptionReporter.class,用來支持報告關(guān)于啟動的錯誤
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
//4黍匾、刷新應(yīng)用上下文前的準(zhǔn)備階段
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//5栏渺、刷新應(yīng)用上下文
this.refreshContext(context);
//刷新應(yīng)用上下文后的擴(kuò)展接口
this.afterRefresh(context, applicationArguments);
//時間記錄停止
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
//發(fā)布容器啟動完成事件
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
具體的每一行代碼的含義請看注釋,我們在這先總結(jié)一下啟動過程中的重要步驟:(筆者傾向于將應(yīng)用上下文同容器區(qū)分開來)
第一步:獲取并啟動監(jiān)聽器
第二步:構(gòu)造應(yīng)用上下文環(huán)境
第三步:初始化應(yīng)用上下文
第四步:刷新應(yīng)用上下文前的準(zhǔn)備階段
第五步:刷新應(yīng)用上下文
第六步:刷新應(yīng)用上下文后的擴(kuò)展接口
OK锐涯,下面SpringBoot的啟動流程分析磕诊,我們就根據(jù)這6大步驟進(jìn)行詳細(xì)解讀。最總要的是第四纹腌,五步霎终。我們會著重的分析。
三壶笼、第一步:獲取并啟動監(jiān)聽器
事件機制在Spring是很重要的一部分內(nèi)容神僵,通過事件機制我們可以監(jiān)聽Spring容器中正在發(fā)生的一些事件,同樣也可以自定義監(jiān)聽事件覆劈。Spring的事件為Bean和Bean之間的消息傳遞提供支持保礼。當(dāng)一個對象處理完某種任務(wù)后,通知另外的對象進(jìn)行某些處理责语,常用的場景有進(jìn)行某些操作后發(fā)送通知炮障,消息、郵件等情況坤候。
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
在這里面是不是看到一個熟悉的方法:getSpringFactoriesInstances()胁赢,可以看下下面的注釋,前面的博文我們已經(jīng)詳細(xì)介紹過該方法是怎么一步步的獲取到META-INF/spring.factories中的指定的key的value白筹,獲取到以后怎么實例化類的智末。
/**
* 通過指定的classloader 從META-INF/spring.factories獲取指定的Spring的工廠實例
* @param type
* @param parameterTypes
* @param args
* @param <T>
* @return
*/
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
// Use names and ensure unique to protect against duplicates
//通過指定的classLoader從 META-INF/spring.factories 的資源文件中,
//讀取 key 為 type.getName() 的 value
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//創(chuàng)建Spring工廠實例
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//對Spring工廠實例排序(org.springframework.core.annotation.Order注解指定的順序)
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
回到run方法徒河,debug這個代碼 SpringApplicationRunListeners listeners = getRunListeners(args); 看一下獲取的是哪個監(jiān)聽器:
EventPublishingRunListener監(jiān)聽器是Spring容器的啟動監(jiān)聽器系馆。
listeners.starting(); 開啟了監(jiān)聽事件。
四顽照、第二步:構(gòu)造應(yīng)用上下文環(huán)境
應(yīng)用上下文環(huán)境包括什么呢由蘑?包括計算機的環(huán)境闽寡,Java環(huán)境,Spring的運行環(huán)境尼酿,Spring項目的配置(在SpringBoot中就是那個熟悉的application.properties/yml)等等爷狈。
首先看一下prepareEnvironment()方法。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
// Create and configure the environment
//創(chuàng)建并配置相應(yīng)的環(huán)境
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
//根據(jù)用戶配置裳擎,配置 environment系統(tǒng)環(huán)境
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach((Environment)environment);
// 啟動相應(yīng)的監(jiān)聽器涎永,其中一個重要的監(jiān)聽器 ConfigFileApplicationListener 就是加載項目配置文件的監(jiān)聽器。listeners.environmentPrepared((ConfigurableEnvironment)environment);
this.bindToSpringApplication((ConfigurableEnvironment)environment);
if (!this.isCustomEnvironment) {
environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
}
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
看上面的注釋鹿响,方法中主要完成的工作土辩,首先是創(chuàng)建并按照相應(yīng)的應(yīng)用類型配置相應(yīng)的環(huán)境,然后根據(jù)用戶的配置抢野,配置系統(tǒng)環(huán)境拷淘,然后啟動監(jiān)聽器,并加載系統(tǒng)配置文件指孤。
4.1启涯、 ConfigurableEnvironment environment = getOrCreateEnvironment();
看看getOrCreateEnvironment()干了些什么。
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
//如果應(yīng)用類型是 SERVLET 則實例化 StandardServletEnvironment
if (this.webApplicationType == WebApplicationType.SERVLET) {
return new StandardServletEnvironment();
}
return new StandardEnvironment();
}
通過代碼可以看到根據(jù)不同的應(yīng)用類型初始化不同的系統(tǒng)環(huán)境實例恃轩。前面咱們已經(jīng)說過應(yīng)用類型是怎么判斷的了结洼,這里就不在贅述了。
從上面的繼承關(guān)系可以看出叉跛,StandardServletEnvironment是StandardEnvironment的子類松忍。這兩個對象也沒什么好講的,當(dāng)是web項目的時候筷厘,環(huán)境上會多一些關(guān)于web環(huán)境的配置鸣峭。
4.2、 configureEnvironment(environment, applicationArguments.getSourceArgs());
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
// 將main 函數(shù)的args封裝成 SimpleCommandLinePropertySource 加入環(huán)境中酥艳。
configurePropertySources(environment, args);
// 激活相應(yīng)的配置文件
configureProfiles(environment, args); 7
}
在執(zhí)行完方法中的兩行代碼后摊溶,debug的截圖如下
如下圖所示,我在spring的啟動參數(shù)中指定了參數(shù):--spring.profiles.active=prod(關(guān)于這個參數(shù)的用法充石,點我莫换,其實就是啟動多個實例用的)
在configurePropertySources(environment, args);中將args封裝成了SimpleCommandLinePropertySource并加入到了environment中。
configureProfiles(environment, args);根據(jù)啟動參數(shù)激活了相應(yīng)的配置文件骤铃。
話不多說拉岁,debug一遍就明白了。
4.3惰爬、 listeners.environmentPrepared(environment);
進(jìn)入到方法一路跟下去就到了SimpleApplicationEventMulticaster類的multicastEvent()方法喊暖。
查看getApplicationListeners(event, type)執(zhí)行結(jié)果,發(fā)現(xiàn)一個重要的監(jiān)聽器ConfigFileApplicationListener补鼻。
先看看這個類的注釋
/**
* {@link EnvironmentPostProcessor} that configures the context environment by loading
* properties from well known file locations. By default properties will be loaded from
* 'application.properties' and/or 'application.yml' files in the following locations:
* <ul>
* <li>classpath:</li>
* <li>file:./</li>
* <li>classpath:config/</li>
* <li>file:./config/:</li>
* </ul>
* <p>
* Alternative search locations and names can be specified using
* {@link #setSearchLocations(String)} and {@link #setSearchNames(String)}.
* <p>
* Additional files will also be loaded based on active profiles. For example if a 'web'
* profile is active 'application-web.properties' and 'application-web.yml' will be
* considered.
* <p>
* The 'spring.config.name' property can be used to specify an alternative name to load
* and the 'spring.config.location' property can be used to specify alternative search
* locations or specific files.
* <p>
* 從默認(rèn)的位置加載配置文件哄啄,并將其加入 上下文的 environment變量中
*/
這個監(jiān)聽器默認(rèn)的從注釋中<ul>標(biāo)簽所示的幾個位置加載配置文件,并將其加入 上下文的 environment變量中风范。當(dāng)然也可以通過配置指定咨跌。
debug跳過 listeners.environmentPrepared(environment); 這一行,查看environment屬性硼婿,果真如上面所說的锌半,配置文件的配置信息已經(jīng)添加上來了。
五寇漫、第三步:初始化應(yīng)用上下文
在SpringBoot工程中刊殉,應(yīng)用類型分為三種,如下代碼所示州胳。
public enum WebApplicationType {
/**
* 應(yīng)用程序不是web應(yīng)用记焊,也不應(yīng)該用web服務(wù)器去啟動
*/
NONE,
/**
* 應(yīng)用程序應(yīng)作為基于servlet的web應(yīng)用程序運行,并應(yīng)啟動嵌入式servlet web(tomcat)服務(wù)器栓撞。
*/
SERVLET,
/**
* 應(yīng)用程序應(yīng)作為 reactive web應(yīng)用程序運行遍膜,并應(yīng)啟動嵌入式 reactive web服務(wù)器。
*/
REACTIVE
}
對應(yīng)三種應(yīng)用類型瓤湘,SpringBoot項目有三種對應(yīng)的應(yīng)用上下文瓢颅,我們以web工程為例,即其上下文為AnnotationConfigServletWebServerApplicationContext弛说。
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_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);
}
我們先看一下AnnotationConfigServletWebServerApplicationContext的設(shè)計挽懦。
關(guān)于他的繼承體系,我們在前面的博客中<Spring IoC容器與應(yīng)用上下文的設(shè)計與實現(xiàn)>已經(jīng)詳細(xì)介紹了木人,在此不再贅述信柿。
應(yīng)用上下文可以理解成IoC容器的高級表現(xiàn)形式,應(yīng)用上下文確實是在IoC容器的基礎(chǔ)上豐富了一些高級功能醒第。
應(yīng)用上下文對IoC容器是持有的關(guān)系角塑。他的一個屬性beanFactory就是IoC容器(DefaultListableBeanFactory)。所以他們之間是持有淘讥,和擴(kuò)展的關(guān)系圃伶。
接下來看GenericApplicationContext類
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private final DefaultListableBeanFactory beanFactory;
...
public GenericApplicationContext() {
this.beanFactory = new DefaultListableBeanFactory();
}
...
}
beanFactory正是在AnnotationConfigServletWebServerApplicationContext實現(xiàn)的接口GenericApplicationContext中定義的。在上面createApplicationContext()方法中的蒲列, BeanUtils.instantiateClass(contextClass) 這個方法中窒朋,不但初始化了AnnotationConfigServletWebServerApplicationContext類,也就是我們的上下文context蝗岖,同樣也觸發(fā)了GenericApplicationContext類的構(gòu)造函數(shù)侥猩,從而IoC容器也創(chuàng)建了。仔細(xì)看他的構(gòu)造函數(shù)抵赢,有沒有發(fā)現(xiàn)一個很熟悉的類DefaultListableBeanFactory欺劳,沒錯唧取,DefaultListableBeanFactory就是IoC容器真實面目了。在后面的refresh()方法分析中划提,DefaultListableBeanFactory是無處不在的存在感枫弟。
debug跳過createApplicationContext()方法。
如上圖所示鹏往,context就是我們熟悉的上下文(也有人稱之為容器淡诗,都可以,看個人愛好和理解)伊履,beanFactory就是我們所說的IoC容器的真實面孔了韩容。細(xì)細(xì)感受下上下文和容器的聯(lián)系和區(qū)別,對于我們理解源碼有很大的幫助唐瀑。在系列文章中群凶,我們也是將上下文和容器嚴(yán)格區(qū)分開來的。