承接上文springboot启动分析二
上篇文章分析到了SpringApplicationRunListener的starting()方法调用流程,今天继续分析SpringApplication类 run方法的后续流程
public ConfigurableApplicationContext run(String... args) {
// 声明并实例化一个跑表,用于计算springboot应用程序启动时间
StopWatch stopWatch = new StopWatch();
// 启动跑表
stopWatch.start();
// 声明一个应用程序上下文,注意这里是一个接口声明
ConfigurableApplicationContext context = null;
// 声明一个集合,用于记录springboot异常报告
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置不在意的属性 java.awt.headless
configureHeadlessProperty();
// 获取用于监听spring应用程序run方法的监听器实例
SpringApplicationRunListeners listeners = getRunListeners(args);
// 循环启动用于run方法的监听器
listeners.starting();
try {
// 封装应用参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 根据SpringApplication实例化时候推断出来的应用类型 webApplicationType,
// 去获取不同的环境,然后获取配置要适用的PropertySource以及激活哪个Profile
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 根据环境配置需要忽略的bean的信息
configureIgnoreBeanInfo(environment);
// 根据环境配置打印banner,即根据bannerMode 枚举值,决定是否打印banner和banner打印的位置
Banner printedBanner = printBanner(environment);
// 创建应用程序上下文,
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)
封装应用参数,即封装命令行传入springboot应用程序的参数到ApplicationArguments对象中
ApplicationArguments 接口
该接口定义了针对应用参数的一系列操作方法,比如获取源参数(即main传入的参数)方法,获取操作名集合(比如源参数为--debug --foo=bar,就会返回["debug","foo"]),是否包含指定操作名方法以及获取操作命令值方法等
DefaultApplicationArguments 类
该类继承自ApplicationArguments 接口,实现了相应操作main参数的方法,类内部定义了两个final属性,source和args
public class DefaultApplicationArguments implements ApplicationArguments {
// 内部类Source 继承 SimpleCommandLinePropertySource
private final Source source;
// main方法传递进来的参数
private final String[] args;
// 构造方法
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 类内部类,继承自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 为内部类,继承自SimpleCommandLinePropertySource
可以看到SimpleCommandLinePropertySource是PropertySource抽象类的派生类
这里new Source() 调用了SimpleCommandLinePropertySource的构造方法
public SimpleCommandLinePropertySource(String... args) {
super(new SimpleCommandLineArgsParser().parse(args));
}
这里利用了SimpleCommandLineArgsParser 简单命令行参数解析器将mian方法传入的args参数解析为CommandLineArgs 对象。为什么需要解析为CommandLineArgs对象,是因为SimpleCommandLineArgsParser类的父类CommandLinePropertySource<CommandLineArgs>是一个泛型抽象类
-
解析参数的方法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参数遍历后,根据"--" 和"="号分割,取出其optionName 和 optionValue值,存入commandLineArgs 的optionArgs属性中。
private final Map<String, List<String>> optionArgs = new HashMap<>();
CommandLineArgs对象构建成功后,继续向上调用父类的构造函数,直到PropertySource抽象类的构造
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参数数组和一个name为"commandLineArgs",value为CommandLineArgs 的PropertySource对象
2. ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
准备应用环境,传入参数一个是SpringApplicationRunListeners ,作用是当环境预备成功后发布ApplicationEnvironmentPreparedEvent 事件;第二个参数是需要根据应用命令行参数配置应用环境,比如命令行参数会改变springboot激活哪个配置文件等
-
预备环境
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment // 1.获取或者创建一个可配置的环境对象ConfigurableEnvironment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 配置环境 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 告诉SpringApplicationRunListener 环境已准备好,可以做出相应的处理了 listeners.environmentPrepared(environment); // 将准备好的环境绑定到springApplication bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()) .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } // 将环境附加到配置属性源中,name为configurationProperties, value为environment对象中的多个PropertySource对象 ConfigurationPropertySources.attach(environment); return environment;
}
-
获取或者创建一个可配置的环境对象ConfigurableEnvironment
private ConfigurableEnvironment getOrCreateEnvironment() { // 如果SpringApplication已经有了环境对象,直接返回 if (this.environment != null) { return this.environment; } // 没有,就根据webApplicationType类型取判断实例化哪一个环境 if (this.webApplicationType == WebApplicationType.SERVLET) { // SERVLET 类型返回StandardServletEnvironment环境对象 return new StandardServletEnvironment(); } if (this.webApplicationType == WebApplicationType.REACTIVE) { return new StandardReactiveWebEnvironment(); } // 否则就返回标准环境对象,非web应用 return new StandardEnvironment();
}
StandardServletEnvironment 标准Servlet环境是 Environment 接口的实现类,用于以Servlet为基础的web应用
这里注意一下,StandardServletEnvironment类继承了StandardEnvironment类,StandardEnvironment类又继承AbstractEnvironment抽象类
这里说一下设计的原因:由于每一种环境在初始化时都需要定义自己环境独有的一些属性,那么就有了一个标准JAVA环境类,该类中会自定义系统属性以及环境变量属性,而Servlet环境则需要在标准环境的基础上增加自己特定的环境属性源如Servlet_config 和servlet context等
实现方式:在AbstractEnvironment抽象类中定义了构造方法,构造器中调用各个子类覆写的customizePropertySources(this.propertySources);函数,这样子类StandardServletEnvironment在实例化new的时候会先调用父类的构造函数,转而调用自己覆写的方法实现
-
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())); }
这样,当我们实例化一个StandardServletEnvironment对象的时候,其实已经初始化Servlet环境默认需要的四个属性源了
getSystemProperties() 内部利用System.getProperties()可以获取到系统属性,如jdk版本等
getSystemEnvironment() 内部利用System.getenv(),可以获取到系统环境变量,
- 配置环境对象
该方法有两个参数,一个是上一步中获取的环境对象,另一个是ApplicationArguments对象的args属性,也就是main方法传入的源String[] args参数
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
// 添加,删除或重新排序此应用环境的任何propertySources
configurePropertySources(environment, args);
// 配置此应用程序环境哪一个配置文件是激活的,只是一个设置的作用
configureProfiles(environment, args);
}
- 告诉SpringApplicationRunListener 环境已经准备好,这一步是重点
listeners.environmentPrepared(environment);
这个和上一篇博客上分析的关于starting方法的发布是一样的逻辑,只是这里不一样的是发布的事件是ApplicationEnvironmentPreparedEvent
这里重点说的是关心这个事件的监听器
ConfigFileApplicationListener
配置上下文环境通过从指定位置加载配置文件,默认属性文件被加载从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;
}
可以看到该监听器,支持的事件源类型为所有,事件类型为ApplicationEnvironmentPreparedEvent和ApplicationPreparedEvent
继续看其监听到事件的处理方式
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
当事件是ApplicationEnvironmentPreparedEvent 时调用私有的onApplicationEnvironmentPreparedEvent方法
继续向下看
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对应的环境处理器实例
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
实例化这三个环境后置处理器后,将ConfigFileApplicationListener 监听器实例也加入这三个处理器之后,排序后再进行循环调用各自的postProcessEnvironment方法进行处理。
下面一一述说这四个环境后置处理器各自做了什么事情
-
SystemEnvironmentPropertySourceEnvironmentPostProcessor
系统环境变量属性源后置处理器@Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { String sourceName = StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME; PropertySource<?> propertySource = environment.getPropertySources() .get(sourceName); // 从已有的环境中获取到name为systemEnvironment的系统环境变量属性源,判断是否为空,不为空就执行if里面的replacePropertySource方法 if (propertySource != null) { replacePropertySource(environment, sourceName, propertySource); } }
继续看替换属性源的方法
实际上就是将原来的系统环境变量属性源对象换成了OriginAwareSystemEnvironmentPropertySource对象,内部的source没有变化
- SpringApplicationJsonEnvironmentPostProcessor
spring应用Json环境后置处理器
该处理器就是用来从spring.application.json解析出JSON格式的配置属性,再添加进enviroment中,key为"spring.application.json",source为Map<String, Object>,这个新的属性比系统的属性system properties优先
- CloudFoundryVcapEnvironmentPostProcessor
貌似是一个关于云平台的环境后置处理器,执行处理逻辑时先判断上下文环境是否时云平台
判断依据是环境变量中是否包含属性“VCAP_APPLICATION”或者“VCAP_SERVICES”,不包含不做处理
- ConfigFileApplicationListener
最后一个也是最重要的一个环境后置处理器,重点分析它到底做了什么
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
// 增加配置文件属性到特定的环境中,参数一为上下文环境,参数二为spring应用类的资源加载器,这里默认为null
addPropertySources(environment, application.getResourceLoader());
}
表明了springboot是利用ConfigFileApplicationListener将项目中的配置文件属性添加到上下文环境中的
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
// 处理配置文件中以"random.XX"的随机数,生成随机值后加入到环境中
RandomValuePropertySource.addToEnvironment(environment);
// new 一个加载器加载配置文件中的所有属性到环境中
new Loader(environment, resourceLoader).load();
}
可以看到在environment中的propertySources属性的propertySourceList中多了name为"random"的随机值PropertySource
Loader 为ConfigFileApplicationListener 的内部类,用于加载候选的属性源以及配置激活文件
// 构造函数传入了上下文环境,以及一个资源加载器
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment;
// 资源加载器为null就new 一个默认的资源加载器
this.resourceLoader = (resourceLoader != null) ? resourceLoader
: new DefaultResourceLoader();
// 属性源加载器则是从spring.factories文件中获取key为PropertySourceLoader的实现类
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接口的实现类,一个PropertiesPropertySourceLoader,一个YamlPropertySourceLoader,分别对应加载properties或xml结尾的文件资源,和yml或yaml结尾的文件资源
下面分析一下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,且不是默认profile
if (profile != null && !profile.isDefaultProfile()) {
addProfileToEnvironment(profile.getName());
}
// 加载激活的profile对应的配置
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
// 加载默认配置
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
// key2 将已经加载的配置属性添加到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);
// 从环境中获取激活的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 将已经加载的配置属性添加到environmenth中
至此,通过ConfigFileApplicationListener 就可以将所有的应用配置文件中的属性添加到environment中了
以后有事件会细分析一下这里的配置文件属性到底怎么解析的
结尾
environment环境准备好的通知事件已经处理完毕,接下来的文章会分析关于applicationContext的创建以及run方法后续的执行流程