承接上文springboot啟動分析二
上篇文章分析到了SpringApplicationRunListener的starting()方法調(diào)用流程,今天繼續(xù)分析SpringApplication類 run方法的后續(xù)流程
public ConfigurableApplicationContext run(String... args) {
// 聲明并實例化一個跑表,用于計算springboot應(yīng)用程序啟動時間
StopWatch stopWatch = new StopWatch();
// 啟動跑表
stopWatch.start();
// 聲明一個應(yīng)用程序上下文,注意這里是一個接口聲明
ConfigurableApplicationContext context = null;
// 聲明一個集合,用于記錄springboot異常報告
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置不在意的屬性 java.awt.headless
configureHeadlessProperty();
// 獲取用于監(jiān)聽spring應(yīng)用程序run方法的監(jiān)聽器實例
SpringApplicationRunListeners listeners = getRunListeners(args);
// 循環(huán)啟動用于run方法的監(jiān)聽器
listeners.starting();
try {
// 封裝應(yīng)用參數(shù)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 根據(jù)SpringApplication實例化時候推斷出來的應(yīng)用類型 webApplicationType,
// 去獲取不同的環(huán)境,然后獲取配置要適用的PropertySource以及激活哪個Profile
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 根據(jù)環(huán)境配置需要忽略的bean的信息
configureIgnoreBeanInfo(environment);
// 根據(jù)環(huán)境配置打印banner,即根據(jù)bannerMode 枚舉值,決定是否打印banner和banner打印的位置
Banner printedBanner = printBanner(environment);
// 創(chuàng)建應(yīng)用程序上下文,
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[]{ConfigurableApplicationContext.class}, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
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;
}
1. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args)
封裝應(yīng)用參數(shù),即封裝命令行傳入springboot應(yīng)用程序的參數(shù)到ApplicationArguments對象中
ApplicationArguments 接口
該接口定義了針對應(yīng)用參數(shù)的一系列操作方法,比如獲取源參數(shù)(即main傳入的參數(shù))方法,獲取操作名集合(比如源參數(shù)為--debug --foo=bar,就會返回["debug","foo"]),是否包含指定操作名方法以及獲取操作命令值方法等
DefaultApplicationArguments 類
該類繼承自ApplicationArguments 接口,實現(xiàn)了相應(yīng)操作main參數(shù)的方法,類內(nèi)部定義了兩個final屬性,source和args
public class DefaultApplicationArguments implements ApplicationArguments {
// 內(nèi)部類Source 繼承 SimpleCommandLinePropertySource
private final Source source;
// main方法傳遞進(jìn)來的參數(shù)
private final String[] args;
// 構(gòu)造方法
public DefaultApplicationArguments(String[] args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}
@Override
public String[] getSourceArgs() {
return this.args;
}
@Override
public Set<String> getOptionNames() {
String[] names = this.source.getPropertyNames();
return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(names)));
}
@Override
public boolean containsOption(String name) {
return this.source.containsProperty(name);
}
@Override
public List<String> getOptionValues(String name) {
List<String> values = this.source.getOptionValues(name);
return (values != null) ? Collections.unmodifiableList(values) : null;
}
@Override
public List<String> getNonOptionArgs() {
return this.source.getNonOptionArgs();
}
/**
* DefaultApplicationArguments 類內(nèi)部類,繼承自SimpleCommandLinePropertySource
* @date 10:53 2018/9/28
*/
private static class Source extends SimpleCommandLinePropertySource {
Source(String[] args) {
super(args);
}
@Override
public List<String> getNonOptionArgs() {
return super.getNonOptionArgs();
}
@Override
public List<String> getOptionValues(String name) {
return super.getOptionValues(name);
}
}
}
- Source source 為內(nèi)部類,繼承自SimpleCommandLinePropertySource

可以看到SimpleCommandLinePropertySource是PropertySource抽象類的派生類
這里new Source() 調(diào)用了SimpleCommandLinePropertySource的構(gòu)造方法
public SimpleCommandLinePropertySource(String... args) {
super(new SimpleCommandLineArgsParser().parse(args));
}
這里利用了SimpleCommandLineArgsParser 簡單命令行參數(shù)解析器將mian方法傳入的args參數(shù)解析為CommandLineArgs 對象。為什么需要解析為CommandLineArgs對象,是因為SimpleCommandLineArgsParser類的父類CommandLinePropertySource<CommandLineArgs>是一個泛型抽象類
-
解析參數(shù)的方法parse
public CommandLineArgs parse(String... args) { CommandLineArgs commandLineArgs = new CommandLineArgs(); for (String arg : args) { if (arg.startsWith("--")) { String optionText = arg.substring(2, arg.length()); String optionName; String optionValue = null; if (optionText.contains("=")) { optionName = optionText.substring(0, optionText.indexOf('=')); optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length()); } else { optionName = optionText; } if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) { throw new IllegalArgumentException("Invalid argument syntax: " + arg); } commandLineArgs.addOptionArg(optionName, optionValue); } else { commandLineArgs.addNonOptionArg(arg); } } return commandLineArgs;}
就是將args參數(shù)遍歷后,根據(jù)"--" 和"="號分割,取出其optionName 和 optionValue值,存入commandLineArgs 的optionArgs屬性中。
private final Map<String, List<String>> optionArgs = new HashMap<>();
CommandLineArgs對象構(gòu)建成功后,繼續(xù)向上調(diào)用父類的構(gòu)造函數(shù),直到PropertySource抽象類的構(gòu)造
public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> {
/** The default name given to {@link CommandLinePropertySource} instances: {@value}. */
public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs";
/** The default name of the property representing non-option arguments: {@value}. */
public static final String DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME = "nonOptionArgs";
private String nonOptionArgsPropertyName = DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME;
/**
* Create a new {@code CommandLinePropertySource} having the default name
* {@value #COMMAND_LINE_PROPERTY_SOURCE_NAME} and backed by the given source object.
*/
public CommandLinePropertySource(T source) {
super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source);
}
}
PropertySource抽象類
spring中,一個用于表述屬性對(name/value)源的抽象類

即最終的DefaultApplicationArguments 對象中含有一個args參數(shù)數(shù)組和一個name為"commandLineArgs",value為CommandLineArgs 的PropertySource對象

2. ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
準(zhǔn)備應(yīng)用環(huán)境,傳入?yún)?shù)一個是SpringApplicationRunListeners ,作用是當(dāng)環(huán)境預(yù)備成功后發(fā)布ApplicationEnvironmentPreparedEvent 事件;第二個參數(shù)是需要根據(jù)應(yīng)用命令行參數(shù)配置應(yīng)用環(huán)境,比如命令行參數(shù)會改變springboot激活哪個配置文件等
-
預(yù)備環(huán)境
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment // 1.獲取或者創(chuàng)建一個可配置的環(huán)境對象ConfigurableEnvironment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 配置環(huán)境 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 告訴SpringApplicationRunListener 環(huán)境已準(zhǔn)備好,可以做出相應(yīng)的處理了 listeners.environmentPrepared(environment); // 將準(zhǔn)備好的環(huán)境綁定到springApplication bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()) .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } // 將環(huán)境附加到配置屬性源中,name為configurationProperties, value為environment對象中的多個PropertySource對象 ConfigurationPropertySources.attach(environment); return environment;}
-
獲取或者創(chuàng)建一個可配置的環(huán)境對象ConfigurableEnvironment
private ConfigurableEnvironment getOrCreateEnvironment() { // 如果SpringApplication已經(jīng)有了環(huán)境對象,直接返回 if (this.environment != null) { return this.environment; } // 沒有,就根據(jù)webApplicationType類型取判斷實例化哪一個環(huán)境 if (this.webApplicationType == WebApplicationType.SERVLET) { // SERVLET 類型返回StandardServletEnvironment環(huán)境對象 return new StandardServletEnvironment(); } if (this.webApplicationType == WebApplicationType.REACTIVE) { return new StandardReactiveWebEnvironment(); } // 否則就返回標(biāo)準(zhǔn)環(huán)境對象,非web應(yīng)用 return new StandardEnvironment();}
StandardServletEnvironment 標(biāo)準(zhǔn)Servlet環(huán)境是 Environment 接口的實現(xiàn)類,用于以Servlet為基礎(chǔ)的web應(yīng)用

這里注意一下,StandardServletEnvironment類繼承了StandardEnvironment類,StandardEnvironment類又繼承AbstractEnvironment抽象類
這里說一下設(shè)計的原因:由于每一種環(huán)境在初始化時都需要定義自己環(huán)境獨有的一些屬性,那么就有了一個標(biāo)準(zhǔn)JAVA環(huán)境類,該類中會自定義系統(tǒng)屬性以及環(huán)境變量屬性,而Servlet環(huán)境則需要在標(biāo)準(zhǔn)環(huán)境的基礎(chǔ)上增加自己特定的環(huán)境屬性源如Servlet_config 和servlet context等
實現(xiàn)方式:在AbstractEnvironment抽象類中定義了構(gòu)造方法,構(gòu)造器中調(diào)用各個子類覆寫的customizePropertySources(this.propertySources);函數(shù),這樣子類StandardServletEnvironment在實例化new的時候會先調(diào)用父類的構(gòu)造函數(shù),轉(zhuǎn)而調(diào)用自己覆寫的方法實現(xiàn)
-
StandardServletEnvironment 類
/** Servlet context init parameters property source name: {@value} */ public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams"; /** Servlet config init parameters property source name: {@value} */ public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams"; /** JNDI property source name: {@value} */ public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties"; @Override protected void customizePropertySources(MutablePropertySources propertySources) { // propertySources 是父類 AbstractEnvironment中的屬性 propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME)); propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) { propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME)); } super.customizePropertySources(propertySources);}
-
super.customizePropertySources(propertySources);
@Override protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_ NAME, getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROP ERTY_SOURCE_NAME, getSystemEnvironment())); }
這樣,當(dāng)我們實例化一個StandardServletEnvironment對象的時候,其實已經(jīng)初始化Servlet環(huán)境默認(rèn)需要的四個屬性源了
getSystemProperties() 內(nèi)部利用System.getProperties()可以獲取到系統(tǒng)屬性,如jdk版本等
getSystemEnvironment() 內(nèi)部利用System.getenv(),可以獲取到系統(tǒng)環(huán)境變量,
- 配置環(huán)境對象
該方法有兩個參數(shù),一個是上一步中獲取的環(huán)境對象,另一個是ApplicationArguments對象的args屬性,也就是main方法傳入的源String[] args參數(shù)
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
// 添加,刪除或重新排序此應(yīng)用環(huán)境的任何propertySources
configurePropertySources(environment, args);
// 配置此應(yīng)用程序環(huán)境哪一個配置文件是激活的,只是一個設(shè)置的作用
configureProfiles(environment, args);
}
- 告訴SpringApplicationRunListener 環(huán)境已經(jīng)準(zhǔn)備好,這一步是重點
listeners.environmentPrepared(environment);
這個和上一篇博客上分析的關(guān)于starting方法的發(fā)布是一樣的邏輯,只是這里不一樣的是發(fā)布的事件是ApplicationEnvironmentPreparedEvent
這里重點說的是關(guān)心這個事件的監(jiān)聽器
ConfigFileApplicationListener
配置上下文環(huán)境通過從指定位置加載配置文件,默認(rèn)屬性文件被加載從application.properties 或者 application.yml
位置:
classpath:
file:./
classpath:config/
file:./config/:
另外的文件也可以被加載基于激活的profiles,例如如果'dev' profile 被激活,那么application-dev.properties 和 application-dev.yml將會被加載
另外使用spring.config.name可以指定加載配置文件的名字,spring.config.location可以指定加載配置文件的位置
利用觀察者模式將配置文件的加載放在了ConfigFileApplicationListener中處理,解耦了加載的過程
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventTy pe)
|| ApplicationPreparedEvent.class.isAssignableFrom(eventType);
}
@Override
public boolean supportsSourceType(Class<?> aClass) {
return true;
}
可以看到該監(jiān)聽器,支持的事件源類型為所有,事件類型為ApplicationEnvironmentPreparedEvent和ApplicationPreparedEvent
繼續(xù)看其監(jiān)聽到事件的處理方式
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
當(dāng)事件是ApplicationEnvironmentPreparedEvent 時調(diào)用私有的onApplicationEnvironmentPreparedEvent方法
繼續(xù)向下看
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
其中的loadPostProcessors()就是去spring.factories文件中獲取EnvironmentPostProcessor.class作為key對應(yīng)的環(huán)境處理器實例
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
實例化這三個環(huán)境后置處理器后,將ConfigFileApplicationListener 監(jiān)聽器實例也加入這三個處理器之后,排序后再進(jìn)行循環(huán)調(diào)用各自的postProcessEnvironment方法進(jìn)行處理。
下面一一述說這四個環(huán)境后置處理器各自做了什么事情
-
SystemEnvironmentPropertySourceEnvironmentPostProcessor
系統(tǒng)環(huán)境變量屬性源后置處理器@Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { String sourceName = StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME; PropertySource<?> propertySource = environment.getPropertySources() .get(sourceName); // 從已有的環(huán)境中獲取到name為systemEnvironment的系統(tǒng)環(huán)境變量屬性源,判斷是否為空,不為空就執(zhí)行if里面的replacePropertySource方法 if (propertySource != null) { replacePropertySource(environment, sourceName, propertySource); } }
繼續(xù)看替換屬性源的方法

實際上就是將原來的系統(tǒng)環(huán)境變量屬性源對象換成了OriginAwareSystemEnvironmentPropertySource對象,內(nèi)部的source沒有變化
- SpringApplicationJsonEnvironmentPostProcessor
spring應(yīng)用Json環(huán)境后置處理器
該處理器就是用來從spring.application.json解析出JSON格式的配置屬性,再添加進(jìn)enviroment中,key為"spring.application.json",source為Map<String, Object>,這個新的屬性比系統(tǒng)的屬性system properties優(yōu)先
- CloudFoundryVcapEnvironmentPostProcessor
貌似是一個關(guān)于云平臺的環(huán)境后置處理器,執(zhí)行處理邏輯時先判斷上下文環(huán)境是否時云平臺
判斷依據(jù)是環(huán)境變量中是否包含屬性“VCAP_APPLICATION”或者“VCAP_SERVICES”,不包含不做處理
- ConfigFileApplicationListener
最后一個也是最重要的一個環(huán)境后置處理器,重點分析它到底做了什么
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
// 增加配置文件屬性到特定的環(huán)境中,參數(shù)一為上下文環(huán)境,參數(shù)二為spring應(yīng)用類的資源加載器,這里默認(rèn)為null
addPropertySources(environment, application.getResourceLoader());
}
表明了springboot是利用ConfigFileApplicationListener將項目中的配置文件屬性添加到上下文環(huán)境中的
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
// 處理配置文件中以"random.XX"的隨機數(shù),生成隨機值后加入到環(huán)境中
RandomValuePropertySource.addToEnvironment(environment);
// new 一個加載器加載配置文件中的所有屬性到環(huán)境中
new Loader(environment, resourceLoader).load();
}

可以看到在environment中的propertySources屬性的propertySourceList中多了name為"random"的隨機值PropertySource
Loader 為ConfigFileApplicationListener 的內(nèi)部類,用于加載候選的屬性源以及配置激活文件
// 構(gòu)造函數(shù)傳入了上下文環(huán)境,以及一個資源加載器
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment;
// 資源加載器為null就new 一個默認(rèn)的資源加載器
this.resourceLoader = (resourceLoader != null) ? resourceLoader
: new DefaultResourceLoader();
// 屬性源加載器則是從spring.factories文件中獲取key為PropertySourceLoader的實現(xiàn)類
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(
PropertySourceLoader.class, getClass().getClassLoader());
}
這里貼一下springboot的META-INF下的sprin.factories
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
會初始化兩個PropertySourceLoader接口的實現(xiàn)類,一個PropertiesPropertySourceLoader,一個YamlPropertySourceLoader,分別對應(yīng)加載properties或xml結(jié)尾的文件資源,和yml或yaml結(jié)尾的文件資源
下面分析一下Load類的load()加載方法
public void load() {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
// key 1 初始化profile配置
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
// 判斷激活profile不是null,且不是默認(rèn)profile
if (profile != null && !profile.isDefaultProfile()) {
addProfileToEnvironment(profile.getName());
}
// 加載激活的profile對應(yīng)的配置
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
// 加載默認(rèn)配置
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
// key2 將已經(jīng)加載的配置屬性添加到environmenth中
addLoadedPropertySources();
}
key1 從environment的激活profiles中初始化profile信息
private void initializeProfiles() {
// The default profile for these purposes is represented as null. We add it
// first so that it is processed first and has lowest priority.
this.profiles.add(null);
// 從環(huán)境中獲取激活的profile
Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
// Any pre-existing active profiles set via property sources (e.g.
// System properties) take precedence over those added in config files.
addActiveProfiles(activatedViaProperty);
if (this.profiles.size() == 1) { // only has null profile
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
key2 將已經(jīng)加載的配置屬性添加到environmenth中

至此,通過ConfigFileApplicationListener 就可以將所有的應(yīng)用配置文件中的屬性添加到environment中了
以后有事件會細(xì)分析一下這里的配置文件屬性到底怎么解析的
結(jié)尾
environment環(huán)境準(zhǔn)備好的通知事件已經(jīng)處理完畢,接下來的文章會分析關(guān)于applicationContext的創(chuàng)建以及run方法后續(xù)的執(zhí)行流程