首先看一个springboot项目的配置,我们可以定义一个application.yml,对于不同的环境有时也通过profile配置项指定不同的配置文件(譬如application-dev.yml),也可以通过命令行覆写具体的VM options配置项(举个栗子,启动时执行 java -jar xxx.jar --server.port=8080),此文讲解这些配制的读取原理。
配置优先级
整体配置项的优先级从高到低为:
命令行配置;
系统属性(System.getProperties())
系统环境变量
jar包外的主配置文件(带有)
jar包内的主配置文件
jar包外的次要配置文件(由spring.profile指定的)
jar包内的次要配置文件(由spring.profile指定的)
启动类主方法
跟随Springboot的启动类run()方法,来看看他做了什么
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//这个方法是重点
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
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);
}
}
我们注意到在执行了prepareEnvironment方法后返回了environment对象,如果我们在此处打断点进行debug,可以发现这个对象其实就是配置的内容,environment对象包含了一个propertySources的对象,里面有一个list属性,在这里我们制定了一个自定义的启动参数(--server.port=xxxx):

例如我们展开其中的第9项,就会看到application.yml转化为的配置。

prepareEnvironment
所以配置文件的初始化其实是在prepareEnvironment方法:
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
//到这里加载完了上面的2 3 4 5
configureEnvironment(environment, applicationArguments.getSourceArgs());
//到这里加载完了上面的0 1
listeners.environmentPrepared(environment);
//到这里全部加载完
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
//一般都为tomcat的servlet容器,进入该分支
if (this.webApplicationType == WebApplicationType.SERVLET) {
return new StandardServletEnvironment();
}
return new StandardEnvironment();
}
在getOrCreateEnvironment方法中初始化了一些基本的source们,这里借助了模板方法的实现,可以看到上面生成的其实是StandardServletEnvironment的实例,他的继承关系如下:

实例化过程在AbstractEnvironment中:
public AbstractEnvironment() {
//这个方法交由子类实现了
customizePropertySources(this.propertySources);
if (logger.isDebugEnabled()) {
logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
}
}
/**
* StandardServletEnvironment 中
* 这里的MutablePropertySources 继承于Iterable,可以理解为一个有序的resource list
*/
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
//addLast顾名思义是插入优先级最低的资源
//对应上面source第2
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
//对应上面source第3
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
//这里调用了StandardEnvironment的方法
super.customizePropertySources(propertySources);
}
/**
* StandardEnvironment 中
* 这里的MutablePropertySources 继承于Iterable,可以理解为一个有序的resource list
*/
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
//对应上面source第4
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
//对应上面source第5
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
configureEnvironment-载入命令行
上文中prepareEnvironment的configureEnvironment方法传入了命令行参数,将上文中1配置项加载进去:
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 = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
//创建source并遍历配置项解析插入
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));
}
}
}
listeners载入配置
通过上面的步骤后已经加载出了下面的五类配置项,其实这里除了命令行就是系统变量,还没有我们自己定义的配置文件中的配置。


