上篇文章分析了PropertySource,描述它是一组属性的集合,算是对接下来Environment环节的铺垫,它可以看做项目的运行时环境,是系统运行的基础,最此之前,我们先来聊一下系统配置的优先级
回想一下之前的面试,有没有遇到过这样的问题:在一个springboot项目中,命令行参数、系统环境变量、配置文件,它们的优先级是怎么样的?这个问题也将在本文中,从源码的角度给出答案
本文的主角,即SpringApplication的run方法中,这行prepareEnvironment
这个方法的入参是SpringApplicationRunListeners和ApplicationArguments,ApplicationArguments我们上一篇文章介绍过,里面封装了应用的启动参数,而SpringApplicationRunListeners之前也分析过,里面存储了一个listeners列表,只包含一个元素EventPublishingRunListener,在前面的流程中,通过它内部的事件多播器,发布了应用启动事件ApplicationStartingEvent
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
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;
}
我们逐行分析,首先是getOrCreateEnvironment
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
} else {
switch(this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
}
通过应用类型来决定实例化哪个Environment的实现类,第一篇文章提到过推断应用类型的方式,普通springboot应用属于SERVLET类型,所以这里创建的是StandardServletEnvironment
它派生自StandardEnvironment,默认构造函数是空的,看到这样的代码,我们一般要跟进一下父类,有可能子类构造函数为空,但是在父类的构造函数中做了一些初始化,并调用了子类的某些方法,本类就属于这种类型
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
......
......
public StandardServletEnvironment() {
}
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
propertySources.addLast(new StubPropertySource("servletContextInitParams"));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource("jndiProperties"));
}
super.customizePropertySources(propertySources);
}
......
......
}
父类的构造函数依然是空,它又派生自AbstractEnvironment
public class StandardEnvironment extends AbstractEnvironment {
......
......
public StandardEnvironment() {
}
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource("systemProperties", this.getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
}
}
继续跟进AbstractEnvironment的默认构造方法
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
......
......
private final MutablePropertySources propertySources = new MutablePropertySources();
private final ConfigurablePropertyResolver propertyResolver;
public AbstractEnvironment() {
this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
this.customizePropertySources(this.propertySources);
}
protected void customizePropertySources(MutablePropertySources propertySources) {
}
......
......
这个构造方法中,先初始化了一个PropertySourcesPropertyResolver,传入了propertySources,这个propertySources是一个final类型,在声明的时候初始化为MutablePropertySources
我们上篇文章单独介绍的PropertySource,是一组属性的集合,而这里的MutablePropertySources,就是PropertySource的容器,它会将系统运行的各个阶段、从不同途径产生的PropertySource,存储在内部的一个List中
public class MutablePropertySources implements PropertySources {
private final List<PropertySource<?>> propertySourceList;
public MutablePropertySources() {
this.propertySourceList = new CopyOnWriteArrayList();
}
......
......
}
而PropertySourcesPropertyResolver,顾名思义,就是一个解析器,它通过持有MutablePropertySources,间接地持有了系统所有的PropertySource,也就可以对其做遍历,进行各种处理、查询等
初始化PropertySourcesPropertyResolver后,紧接着调用了customizePropertySources方法,把MutablePropertySources传了进去,此时MutablePropertySources里面的PropertySource列表还是空的
由于这个方法在子类中也有实现,所以真正调用的是具体实现类型StandardServletEnvironment中的方法
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
propertySources.addLast(new StubPropertySource("servletContextInitParams"));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource("jndiProperties"));
}
super.customizePropertySources(propertySources);
}
通过调用MutablePropertySources 的addLast方法,往内部PropertySource的列表末尾添加了两组空配置,名称分别为servletConfigInitParams和servletContextInitParams,JNDI现在用的很少了,所以后面这行暂且跳过
然后调用了父类的customizePropertySources方法
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource("systemProperties", this.getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
}
也添加了两组配置,其中systemProperties是系统属性,包括诸如JVM版本、JDK版本、启动的时候通过-D指定的参数等都在这里,取的是System.getProperties()
public Map<String, Object> getSystemProperties() {
try {
return System.getProperties();
}
......
}
比如在启动类通过-D添加系统属性
解析后加载到systemProperties中
第二行systemEnvironment,添加的是系统的环境变量,包含我们配置的JAVA_HOME等
比如添加环境变量
然后解析到了systemEnvironment中
systemProperties和systemEnvironment解析完成后,getOrCreateEnvironment方法就结束了,最终返回了一个Environment,内部有四个PropertySource,前两个是空的,后两个分别存储了系统属性和环境变量
回到prepareEnvironment方法,再看下一行configureEnvironment
传入的第一个参数为刚创建好的Environment,第二个参数就是启动参数String args[]
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService)conversionService);
}
this.configurePropertySources(environment, args);
this.configureProfiles(environment, args);
}
addConversionService属性在初始化SpringApplication的时候默认设置为true,所以会进if分支,创建了一个ApplicationConversionService,并赋给environment
这个ApplicationConversionService是一个转换器,负责类型转换等工作,我们之前在分析ApplicationStartingEvent的时候,有提到过一个监听器BackgroundPreinitializer,在该事件中会启动一些线程做一些初始化工作,其中有个线程,new了一个DefaultFormattingConversionService,它和当前的ApplicationConversionService有共同的继承结构,只是之前仅仅new了一个实例,并没有存储,目的是让JVM提前把相关的Class信息装载起来,以及提前完成一些静态变量、静态代码块的初始化工作,而这里的实例赋给了environment后续使用
然后看下一行代码configurePropertySources
目的就是把命令行参数也作为一个PropertySource存进environment中
这里最先初始化完成的environment是不包含名为commandLineArgs的配置的,所以会走else分支,调用addFirst方法,SimpleCommandLinePropertySource的构造流程我们上篇文章看过,最终会生成一个名为commandLineArgs的PropertySource
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
}
if (this.addCommandLineProperties && args.length > 0) {
String name = "commandLineArgs";
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
} else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
这里有意思的就来了,我们之前往PropertySource列表添加配置,都是调用的addLast方法,而这里调用了addFirst
我们看下MutablePropertySources的方法列表,发现除了addFirst、addLast,还有addBefore、addAfter方法,可以在指定位置插入一个PropertySource
public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
this.assertLegalRelativeAddition(relativePropertySourceName, propertySource);
this.removeIfPresent(propertySource);
int index = this.assertPresentAndGetIndex(relativePropertySourceName);
this.addAtIndex(index, propertySource);
}
public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {
this.assertLegalRelativeAddition(relativePropertySourceName, propertySource);
this.removeIfPresent(propertySource);
int index = this.assertPresentAndGetIndex(relativePropertySourceName);
this.addAtIndex(index + 1, propertySource);
}
本文开头分析过,MutablePropertySources持有所有的PropertySource,而初始化Environment的时候新建了一个解析器PropertySourcesPropertyResolver,把MutablePropertySources传给了它,那么这个Resolver是怎么获取一个属性呢,看下它的getProperty方法
@Nullable
public String getProperty(String key) {
return (String)this.getProperty(key, String.class, true);
}
@Nullable
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
Iterator var4 = this.propertySources.iterator();
while(var4.hasNext()) {
PropertySource<?> propertySource = (PropertySource)var4.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = this.resolveNestedPlaceholders((String)value);
}
this.logKeyFound(key, propertySource, value);
return this.convertValueIfNecessary(value, targetValueType);
}
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Could not find key '" + key + "' in any property source");
}
return null;
}
遍历内部的PropertySource列表,依次调用其getProperty方法,获取到属性后就返回,后面的PropertySource中即使有同名属性也不再处理了,看上去就像是前面的属性“覆盖”了后面的同名属性
List是个有序集合,所以PropertySource添加到List中的顺序,就决定了它的优先级,上面的命令行参数添加到List中使用的方法是addFirst,插入到了集合的最前面,所以命令行参数的优先级是最高的
既然了解了这个机制,想要自定义一批属性,让它拥有最高的优先级,只需要将其添加到PropertySource列表的最前面,而SpringBoot也提供了这样的扩展点,我们下篇文章再做介绍