public abstract class PropertySource<T> {
protected final String name; // application.properties 或 bootstrap.properties
protected final T source; // 配置文件中的内容
public final class OriginTrackedMapPropertySource extends MapPropertySource
implements OriginLookup<String> {
@SuppressWarnings({ "unchecked", "rawtypes" })
public OriginTrackedMapPropertySource(String name, Map source) {
super(name, source);
}
→ SpringApplication#prepareEnvironment() 准备环境
→ SpringApplicationRunListeners#environmentPrepared() 环境准备完毕,发布 ApplicationEnvironmentPreparedEvent 事件
→ ConfigFileApplicationListener#onApplicationEvent() 监听事件
→ ConfigFileApplicationListener#postProcessEnvironment()
→ ConfigFileApplicationListener#addPropertySources()
→ ConfigFileApplicationListener.Loader#load()
→ ... 这里各种 load..() ,不回溯栈帧还真不好分析
→ ConfigFileApplicationListener.Loader#loadDocuments()
→ PropertiesPropertySourceLoader#load() PropertySourceLoader 的实现类
方法最后会将 配置文件名 和 下面返回的 Map 封装到 OriginTrackedMapPropertySource 对象中
→ PropertiesPropertySourceLoader#loadProperties()
→ OriginTrackedPropertiesLoader#load() 将配置文件中的 K-V 添加到一个 Map<String, OriginTrackedValue> 集合中并返回
application.properties
hello=application
...
bootstrap.properties
hello=bootstrap
...
测试结果:真正起作用的是 application.properties
中配置的值。
ConfigFileApplicationListener
是用来处理配置文件的(看名字也能看出来),跟踪其源码,最终是通过其内部类 ConfigFileApplicationListener.Loader#addLoadedPropertySource()
向环境中添加封装了 application.properties
的 PropertySource
public class ConfigFileApplicationListener
implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
private static final String DEFAULT_PROPERTIES = "defaultProperties";
// Note the order is from least to most specific (last one wins)
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
private static final String DEFAULT_NAMES = "application";
...
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
======> onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
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());
}
}
...
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
======> addPropertySources(environment, application.getResourceLoader());
}
...
/**
* Add config file property sources to the specified environment.
* @param environment the environment to add source to
* @param resourceLoader the resource loader
* @see #addPostProcessors(ConfigurableApplicationContext)
*/
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
======> new Loader(environment, resourceLoader).load();
}
...
/**
* Loads candidate property sources and configures the active profiles.
*/
private class Loader {
...
public void load() {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
resetEnvironmentProfiles(this.processedProfiles);
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
======> addLoadedPropertySources();
}
...
private void addLoadedPropertySources() {
MutablePropertySources destination = this.environment.getPropertySources();
List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
Collections.reverse(loaded);
String lastAdded = null;
Set<String> added = new HashSet<>();
for (MutablePropertySources sources : loaded) {
for (PropertySource<?> source : sources) {
if (added.add(source.getName())) {
======> addLoadedPropertySource(destination, lastAdded, source);
lastAdded = source.getName();
}
}
}
}
private void addLoadedPropertySource(MutablePropertySources destination,
String lastAdded, PropertySource<?> source) {
if (lastAdded == null) {
if (destination.contains(DEFAULT_PROPERTIES)) {
// 将封装了 application.properties 的 PropertySource 添加到 "defaultProperties" 之前
======> destination.addBefore(DEFAULT_PROPERTIES, source);
}
else {
destination.addLast(source);
}
}
else {
destination.addAfter(lastAdded, source);
}
}
}
...
}
ApplicationContextInitializer
接口,通过重写其 initialize(ConfigurableApplicationContext)
方法,以硬代码的方式添加 PropertySource
,既然这样,那 SpringCloud 内部是不是也是这样把封装了 bootstrap.properties
的 PropertySource
添加到容器的 ConfigurableEnvironment
中的呢?本着这个猜想,找到了BootstrapApplicationListener.AncestorInitializer
类:public class BootstrapApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
...
private static class AncestorInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
...
@Override
public void initialize(ConfigurableApplicationContext context) {
while (context.getParent() != null && context.getParent() != context) {
context = (ConfigurableApplicationContext) context.getParent();
}
======> reorderSources(context.getEnvironment());
new ParentContextApplicationContextInitializer(this.parent)
.initialize(context);
}
private void reorderSources(ConfigurableEnvironment environment) {
// 从 SpringBoot 上下文的环境变量中移除键为 "defaultProperties" 的 ExtendeDefaultPropertySource
PropertySource<?> removed = environment.getPropertySources()
.remove(DEFAULT_PROPERTIES);
if (removed instanceof ExtendedDefaultPropertySource) {
ExtendedDefaultPropertySource defaultProperties = (ExtendedDefaultPropertySource) removed;
// 向 SpringBoot 上下文的环境变量中添加键为 "defaultProperties" 值为 LinkedHashMap (size=0,其实就是一个空的 Map) 的 PropertySource
environment.getPropertySources().addLast(new MapPropertySource(
DEFAULT_PROPERTIES, defaultProperties.getSource()));
// 遍历刚才移除的 ExtendedDefaultPropertySource(该对象内部包含封装了 bootstrap.properties 的 OriginTrackedMapPropertySource)
for (PropertySource<?> source : defaultProperties.getPropertySources()
.getPropertySources()) {
if (!environment.getPropertySources().contains(source.getName())) {
// 将封装了 bootstrap.properties 的 PropertySource 添加到 "defaultProperties" 之前
======> environment.getPropertySources().addBefore(DEFAULT_PROPERTIES,
source);
}
}
}
}
}
}
有人会疑问:既然都是加到 defaultProperties
之前,那到底谁在前谁在后呢?
这就需要大家了解 SpringBoot 启动流程(具体流程不再详述):
SpringApplication#run()
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 准备容器环境
// 这一步会将封装 application.properties 的 PropertySource 对象添加到环境变量中
======> ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备容器
// 这一步会将封装 bootstrap.properties 的 PropertySource 对象添加到环境变量中
======> 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;
}
SpringApplication#prepareEnvironment(...)
准备环境
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 调用所有 SpringApplicationRunListener 的 environmentPrepared() 方法
// 使用 SimpleApplicationEventMulticaster 对象发布 ApplicationEnvironmentPreparedEvent事件
// 通知所有监听此事件的 ApplicationListener ,包括 BootstrapApplicationListener 和 ConfigFileApplicationListener
======> listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
SpringApplication#prepareContext(...)
准备容器
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 调用当前 SpringApplication 对象中所有 ApplicationContextInitializer 的
// initialize 方法,包括 BootstrapApplicationListener.AncestorInitializer
======> applyInitializers(context);
...
}
分析流程得出:
application.properties
的 PropertySource
添加到键为 "defaultProperties"
的前面;bootstrap.properties
的 PropertySource
添加到键为 "defaultProperties"
的前面;"defaultProperties"
对应的 PropertySource
不一样,前者是 ExtendedDefaultPropertySource
,后者是 MapPropertySource
,但这不影响结果的顺序;defaultProperties
值为 ExtendedDefaultPropertySource
的对象,而 ExtendedDefaultPropertySource 对象内包含着封装了 bootstrap.properties
的 OriginTrackedMapPropertySource
,分析源代码可以看到,最终会把OriginTrackedMapPropertySource
从 ExtendedDefaultPropertySource
中取出,然后直接添加到 SpringBoot 上下文的环境变量中application.properties
和 bootstrap.properties
的 PropertySource
才最终被添加到容器环境中,也就是说是在 SpringBoot 启动流程中完成添加的。间接说明了在 application.properties
中的配置项不会影响 SpringCloud 的启动流程,如在 application.properties
中配置的 spring.cloud.bootstrap.enabled=false
不会起作用,根本原因是 BootstrapApplicationListener
的优先级高于 ConfigFileApplicationListener
BootstrapApplicationListener.AncestorInitializer
类并没有被配置在 META-INF/spring.factories
文件中,它是在 SpringCloud 启动流程中被保存到 SpringBoot 的 SpringApplication
对象中的;xxEnvironment
内部使用 MutablePropertySources
保存 PropertySource
,本文分析时把中间环节 environment.getPropertySources()
省略了。本来想着分析的细一点儿,但分着分着几乎要把 SpringBoot 和 SpringCloud 两大启动流程分析个遍,碍于时间精力和对源码的了解程度,遂只介绍了一些思路和关键点,如有不当之处,还请指点一二。