目录:Springboot源码学习目录
上文:04、SpringBoot 启动 准备运行环境(prepareEnvironment)流程(一)
前言:上文中有一个最重要的点,就是发送准备完成事件,这一步里,就有对我们平时用到的 application.properties/application.yaml的配置文件解析,我们再本篇文章中重度讲解
一、debug进入发送环境已准备事件
listeners.environmentPrepared(bootstrapContext, environment);
在SpringApplicationRunListeners
中,会调用所有的SpringApplicationRunListener
,其实在只有一个实现类EventPublishingRunListener
// 首先进入这个方法
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
doWithListeners("spring.boot.application.environment-prepared",
(listener) -> listener.environmentPrepared(bootstrapContext, environment));
}
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) {
doWithListeners(stepName, listenerAction, null);
}
// 最终进入的方法
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
Consumer<StartupStep> stepAction) {
StartupStep step = this.applicationStartup.start(stepName);
// 遍历所有的SpringApplicationRunListener,执行environmentPrepared方法
this.listeners.forEach(listenerAction);
if (stepAction != null) {
stepAction.accept(step);
}
step.end();
}
因为SpringApplicationRunListener
只有一个实现EventPublishingRunListener
,我们就看这一个就可以了
需要注意的是这个Spring应用运行监听器在实例化的过程中还初始化一个广播器,SimpleApplicationEventMulticaster
,
后续就是用这个广播器向所有的ApplicationListener
发送事件
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
// 调用广播器,广播事件
this.initialMulticaster.multicastEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
debug进入广播器
public void multicastEvent(ApplicationEvent event) {
// 解析事件类型,继续调用广播事件重载方法
multicastEvent(event, resolveDefaultEventType(event));
}
``
debug进入广播事件重载方法
```java
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
// 获取一个线程池,如果广播器中有,则事件发的发送都是异步执行,当前这个广播器中没有线程池,所以都是同步执行
Executor executor = getTaskExecutor();
// 获取到所有ApplicationListener,遍历调用,下面有截图获取到的所有监听器
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
// 因为executor为null,肯定走的这里
invokeListener(listener, event);
}
}
}
上面图片里是获取到的监听器,一共有6个,而最重要的是EnvironmentPostProcessorApplicationListener
,而我们后面只需要看这一个监听器就可以了
debug进入invokeListener
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
// 这里 errorHandler 也是null
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
// 走到这个位置,真正执行的地方
doInvokeListener(listener, event);
}
}
debug进入doInvokeListener
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
// 执行事件回调方法
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
//省略不重要逻辑
}
}
前面我们也说的,一共获取到6个监听器,我们现在只需看EnvironmentPostProcessorApplicationListener
这一个监听器就可以
debug进入EnvironmentPostProcessorApplicationListener
的onApplicationEvent方法
public void onApplicationEvent(ApplicationEvent event) {
// 前面发送的就是ApplicationEnvironmentPreparedEvent事件,进入这里
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent((ApplicationFailedEvent) event);
}
}
debug进入onApplicationEnvironmentPreparedEvent
,这里面会获取环境后置处理器,也可以叫环境增强器,EnvironmentPostProcessor
然后遍历执行
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
// 获取环境后置处理器,遍历执行,其实我们真正关注的,是对配置文件进行解析的那个,也就是ConfigDataEnvironmentPostProcessor
// 下面图片中会展示获取到的所有的后置环境增强器
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(event.getBootstrapContext())) {
postProcessor.postProcessEnvironment(environment, application);
}
}
上面图片中所有的EnvironmentPostProcessor
中我们只需关系ConfigDataEnvironmentPostProcessor
,后面也只需要debug这个就可以
在springboot2.4之前的配置文件解析是ConfigFileApplicationListener
这个类
但是2.4后springboot对配置文件的用法做了一次修改,2.4之后就是ConfigDataEnvironmentPostProcessor
这个类对配置文件进行解析
我们debug找到ConfigDataEnvironmentPostProcessor
,进入postProcessEnvironment
方法
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 进入重载方法
postProcessEnvironment(environment, application.getResourceLoader(), application.getAdditionalProfiles());
}
继续debug,里面分为两大步
1、获取ConfigDataEnvironment
对象
2、执行ConfigDataEnvironment
对象中的processAndApply
方法
void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
try {
this.logger.trace("Post-processing environment to add config data");
resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();
}
catch (UseLegacyConfigProcessingException ex) {
// 省略不重要逻辑
}
}
二、获取ConfigDataEnvironment
对象
debug进入getConfigDataEnvironment
,我们看到创建了一个ConfigDataEnvironment,而我们就是要看他的实例化过程
ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader,
additionalProfiles, this.environmentUpdateListener);
}
首先是初始化常量,这里我只列重要的,后面会出现的,为了方便讲解,我会调整顺序顺序,源码里面的常量顺序不一样
// 默认的配置文件所在目录路径,下面的静态代码块是向其中加入的配置文件目录路径
// 注意,只会读取配置路径目录下面的以 application 开头以 .properties /.yaml /.yml 结尾的配置文件
static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
// 加入的默认路径,
// 需要注意的是,下面加入的目录路径前面都有,optional关键字,
// 如果路径前加optional,则说明该配置可有可以无,不加则说明必须有,没有会报错
static {
List<ConfigDataLocation> locations = new ArrayList<>();
// 类路径下,或者类路径下的config目录,就是我们常见的resource下
locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
// 当前项目根目录下,或者当前项目跟目录下的,config目录下
locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
}
// 覆盖默认位置的配置文件,也就是使默认的配置路径下的配置文件失效,有两种用法
// 1、配置一个目录,会自动读取目录下的 以 application 开头以 .properties /.yaml /.yml 结尾的配置文件,
// 并且如果目录下不存在满足条件的配置文件,配置路径前面加不加optional关键字都不会报错,会正常启动,这种情况会当做没有配置文件启动,哪怕默认位置也有配置文件
//
// 2、直接配置一个配置文件,配置文件可以用任意名称作为配置文件的名字,不用必须是 appplication开头
// 但是直接配置的配置文件,如果不存在,会报错,需要加上optional关键字,才能正常启动,这种情况会当做没有配置文件启动,哪怕默认位置也有配置文件
static final String LOCATION_PROPERTY = "spring.config.location";
// 使用指定位置目录下的application.properties配置文件,默认位置的配置文件也生效,也是有两种用法
// 1、配置一个目录,会自动读取目录下的 以 application 开头以 .properties /.yaml /.yml 结尾的配置文件,
// 并且如果目录下不存在满足条件的配置文件,配置路径前面加不加optional关键字都不会报错,会正常启动,这种情况只有默认位置的配置文件生效(前提默认位置也有配置文件,否则也是无配置文件启动)
//
// 2、直接配置一个配置文件,配置文件可以用任意名称作为配置文件的名字,不用必须是 appplication开头
// 但是直接配置的配置文件,如果不存在,会报错,需要加上optional关键字,才能正常启动,这种情况只有默认位置的配置文件生效(前提默认位置也有配置文件,否则也是无配置文件启动)
static final String ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
// 导入指定配置文件,和spring.config.additional-location的用法一模一样,可能有不一样的地方,我没找到,有大佬看到可以支出
// 不过一般spring.config.import多配置在配置文件中,而spring.config.additional-location多用于命令行
static final String IMPORT_PROPERTY = "spring.config.import";
// 是一个全局的配置,如果指定的配置文件不存在如何处理,该配置可以配置为 fail 或者 ignore,默认为fail
// fail 找不到对应的配置文件则报错,中断启动
// ignore 找不到对应的配置文件忽略,继续启动
static final String ON_NOT_FOUND_PROPERTY = "spring.config.on-not-found";
// 空路径,后续解析各个配置下的配置文件,如果不存在配置文件,则返回这个空路径
private static final ConfigDataLocation[] EMPTY_LOCATIONS = new ConfigDataLocation[0];
// 配置文件位置数据模板,后面将所有的配置文件的位置都会根据这个常量解析成ConfigDataLocation类型
private static final Bindable<ConfigDataLocation[]> CONFIG_DATA_LOCATION_ARRAY = Bindable.of(ConfigDataLocation[].class);
private static final Bindable<List<String>> STRING_LIST = Bindable.listOf(String.class);
private static final BinderOption[] ALLOW_INACTIVE_BINDING = {};
private static final BinderOption[] DENY_INACTIVE_BINDING = { BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE };
然后是执行构造方法里面的初始化操作,到这我们先提前注意几个点,防止后面混乱
配置文件路径解析器集合类ConfigDataLocationResolvers
,和配置文件解析器类ConfigDataLocationResolver
不是同一种类型,ConfigDataLocationResolvers
里面有个集合,存放了所有的ConfigDataLocationResolver
同理还有配置文件加载器集合类ConfigDataLoaders
和配置文件加载器类ConfigDataLoader
,配置数据环境贡献者集合类ConfigDataEnvironmentContributors
和配置数据环境贡献者类ConfigDataEnvironmentContributor
配置数据环境贡献者集合类和配置数据环境贡献者类和其他两个又有点区别,配置数据环境贡献者集合对象中放了一个Root配置数据环境贡献者对象,
Root配置数据环境贡献者对象里面存了一个map,key为ImportPhase.BEFORE_PROFILE_ACTIVATION
,value是一个list集合,存放了所有的配置数据环境贡献者ConfigDataEnvironmentContributor
,这些我们再后面会详解结束
ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles,
ConfigDataEnvironmentUpdateListener environmentUpdateListener) {
// 将环境对象(environment)转换成Binder类型,便于配置的操作
Binder binder = Binder.get(environment);
// 2.4版本后配置文件进行了大变革,这里和旧的配置有关,此处用不到,不用关系
UseLegacyConfigProcessingException.throwIfRequested(binder);
this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass());
// 全局配置,没有找到对应的配置文件该如何操作,默认是失败,如果spring.config.on-not-found的配置文ignore,则会忽略,无论配置文件前面是否有添加optional
this.notFoundAction = binder.bind(ON_NOT_FOUND_PROPERTY, ConfigDataNotFoundAction.class)
.orElse(ConfigDataNotFoundAction.FAIL);
this.bootstrapContext = bootstrapContext;
this.environment = environment;
// 初始化配置文件路径解析器集合对象,见2.1
this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
// 附加配置文件,为空
this.additionalProfiles = additionalProfiles;
// 环境更新监听器,传入的environmentUpdateListener为空,初始化一个空的监听器
this.environmentUpdateListener = (environmentUpdateListener != null) ? environmentUpdateListener
: ConfigDataEnvironmentUpdateListener.NONE;
// 初始化配置文件加载器集合对象 见2.2
this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext);
// 初始化配置文件贡献者集合对象 见2.3,最重要
this.contributors = createContributors(binder);
}
2.1、初始化配置文件路径解析器集合对象
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
ConfigurableBootstrapContext bootstrapContext, Binder binder, ResourceLoader resourceLoader) {
// 创建一个ConfigDataLocationResolvers对象
return new ConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
}
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
Binder binder, ResourceLoader resourceLoader) {
// 调用重载构造方法,从spring.factory中获取ConfigDataLocationResolver类型的所有实现
this(logFactory, bootstrapContext, binder, resourceLoader,
SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, ConfigDataLocationResolver.class.getClassLoader()));
}
上面从spring.factory中获取ConfigDataLocationResolver
类型的所有实现有两个
names里面是 StandardConfigDataLocationResolver
,ConfigTreeConfigDataLocationResolver
继续debug
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
Binder binder, ResourceLoader resourceLoader, List<String> names) {
Instantiator<ConfigDataLocationResolver<?>> instantiator = new Instantiator<>(ConfigDataLocationResolver.class,
(availableParameters) -> {
availableParameters.add(Log.class, logFactory::getLog);
availableParameters.add(DeferredLogFactory.class, logFactory);
availableParameters.add(Binder.class, binder);
availableParameters.add(ResourceLoader.class, resourceLoader);
availableParameters.add(ConfigurableBootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapRegistry.class, bootstrapContext);
});
// 将names中的类进行实例化,并且调reorder方法进行一下转换
this.resolvers = reorder(instantiator.instantiate(names));
}
就是将ConfigDataLocationResolver
进行一次转换,然后返回一个不可变集合
private List<ConfigDataLocationResolver<?>> reorder(List<ConfigDataLocationResolver<?>> resolvers) {
List<ConfigDataLocationResolver<?>> reordered = new ArrayList<>(resolvers.size());
StandardConfigDataLocationResolver resourceResolver = null;
for (ConfigDataLocationResolver<?> resolver : resolvers) {
if (resolver instanceof StandardConfigDataLocationResolver) {
resourceResolver = (StandardConfigDataLocationResolver) resolver;
}
else {
reordered.add(resolver);
}
}
if (resourceResolver != null) {
reordered.add(resourceResolver);
}
return Collections.unmodifiableList(reordered);
}
2.2、初始化配置文件加载器集合对象
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext) {
/ 调用重载构造方法,从spring.factory中获取ConfigDataLoader类型的所有实现
this(logFactory, bootstrapContext, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null));
}
上面从spring.factory中获取ConfigDataLoader
类型的所有实现有两个
names里面是 StandardConfigDataLoader
,ConfigTreeConfigDataLoader
继续debug
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
List<String> names) {
this.logger = logFactory.getLog(getClass());
Instantiator<ConfigDataLoader<?>> instantiator = new Instantiator<>(ConfigDataLoader.class,
(availableParameters) -> {
availableParameters.add(Log.class, logFactory::getLog);
availableParameters.add(DeferredLogFactory.class, logFactory);
availableParameters.add(ConfigurableBootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapRegistry.class, bootstrapContext);
});
// 将names中的类进行实例化
this.loaders = instantiator.instantiate(names);
// 在根据loader获取对应的资源类型
this.resourceTypes = getResourceTypes(this.loaders);
}
2.3、初始配置数据环境贡献者集合对象
在讲这个配置数据环境贡献者ConfigDataEnvironmentContributor
之前我们先说明白这个是什么
首先应用运行的时候,所有的配置都存在环境对象中(environment),而环境对象中在前面文章中我们也出现过,会存在PropertySource
集合,而后面出现的配置文件,最终也会解析成PropertySource
对象,存入环境中的PropertySource
集合
在初始化贡献者之前我们再看看现在环境中PropertySource
集合有哪些PropertySource
可以看到上图中有7条PropertySource
,而这7条属性源都会解析成ConfigDataEnvironmentContributor
,用来帮助后续配置文件的解析,因为解析配置文件的时候,会用到之前已经存在的配置
ConfigDataEnvironmentContributor
还区分类型,在debug的过程中就能看见每一个贡献者的角色,有以下几种
- ROOT:根贡献者,本身提供任何配置,但是有的贡献者都在root贡献者里存储
- EXISTING:已存在贡献者,在初始化配置文件之前,也就是创建配置数据环境贡献者集合对象之前就存在的配置,比如上图中已经存在的属性源对象
PropertySource
,解析成的贡献者 - INITIAL_IMPORT:初始导入贡献者,在配置数据环境贡献者集合对象创建完成后,由spring.config.location,spring.config.additional-location,spring.config.import,指定的路径或者默认的配置路径,此贡献者用于解析出其他贡献者,本身不提供任何配置
- UNBOUND_IMPORT:未导入完成贡献者,已经解析的贡献者,但是本身可能会解析出更多贡献者
- BOUND_IMPORT:导入完成贡献者,解析完成的贡献者,里面已经有属性源
- EMPTY_LOCATION:空的贡献者
private ConfigDataEnvironmentContributors createContributors(Binder binder) {
this.logger.trace("Building config data environment contributors");
MutablePropertySources propertySources = this.environment.getPropertySources();
List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(propertySources.size() + 10);
PropertySource<?> defaultPropertySource = null;
for (PropertySource<?> propertySource : propertySources) {
if (DefaultPropertiesPropertySource.hasMatchingName(propertySource)) {
defaultPropertySource = propertySource;
}
else {
this.logger.trace(LogMessage.format("Creating wrapped config data contributor for '%s'",
propertySource.getName()));
// 将环境中已有的属性源PropertySource解析为已存在贡献者,见2.3.1
contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));
}
}
// 将启动参数中的spring.config.location,spring.config.additional-location,spring.config.import
// 等配置指定的路径解析成初始导入贡献者 见2.3.2
contributors.addAll(getInitialImportContributors(binder));
if (defaultPropertySource != null) {
this.logger.trace("Creating wrapped config data contributor for default property source");
contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
}
// 将所有的贡献者传入,创建一个贡献者集合对象,见2.3.3
return createContributors(contributors);
}
2.3.1、解析已存在贡献者
由已经存在的PropertySource
,解析成的贡献者,未其他贡献者解析提供初始配置
static ConfigDataEnvironmentContributor ofExisting(PropertySource<?> propertySource) {
return new ConfigDataEnvironmentContributor(Kind.EXISTING, null, null, false, propertySource,
ConfigurationPropertySource.from(propertySource), null, null, null);
}
2.3.2、解析初始导入贡献者
将启动参数中的spring.config.location,spring.config.additional-location,spring.config.import配置的配置文件路径解析为初始导入贡献者
// bindLocations方法解析见 2.3.2.1
// addInitialImportContributors方法解析见 2.3.2.2
private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
// 导入贡献者集合,收集后面由启动参数创建的贡献者
List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
// IMPORT_PROPERTY就是spring.config.import,EMPTY_LOCATIONS就是一个空的路径对象
// 从启动参数里获取spring.config.import配置,如果有,则创建一个配置路径对象ConfigDataLocation,没有就使用空的配置路径对象
addInitialImportContributors(initialContributors,
bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
// ADDITIONAL_LOCATION_PROPERTY就是spring.config.additional,EMPTY_LOCATIONS就是一个空的路径对象
// 从启动参数里获取spring.config.additional配置,如果有,则创建一个配置路径对象ConfigDataLocation,没有就使用空的配置路径对象
addInitialImportContributors(initialContributors,
bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
// LOCATION_PROPERTY就是spring.config.location,
// DEFAULT_SEARCH_LOCATIONS里就是默认的路径对象,里面有两个路径对象,optional:classpath:/;optional:classpath:/config/和optional:file:./;optional:file:./config/;optional:file:./config/*/
// 从启动参数里获取spring.config.location配置,如果有,则创建一个配置路径对象ConfigDataLocation,没有就使用默认的配置路径对象
addInitialImportContributors(initialContributors,
bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));
return initialContributors;
}
2.3.2.1、构建路径对象
// binder 由环境对象解析而来,里面有所有的系统环境变量,jvm环境变量,启动参数变量
// propertyName 要从环境中获取的key
// other 如果获取不到,则返回的默认对象
private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, ConfigDataLocation[] other) {
return binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other);
}
2.3.2.2、创建初始导入贡献者对象
// initialContributors 收集贡献者
// locations 要解析为贡献者的路径对象
private void addInitialImportContributors(List<ConfigDataEnvironmentContributor> initialContributors,
ConfigDataLocation[] locations) {
for (int i = locations.length - 1; i >= 0; i--) {
initialContributors.add(createInitialImportContributor(locations[i]));
}
}
// 将传入的配置数据路径对象转为 初始导入贡献者
private ConfigDataEnvironmentContributor createInitialImportContributor(ConfigDataLocation location) {
this.logger.trace(LogMessage.format("Adding initial config data import from location '%s'", location));
return ConfigDataEnvironmentContributor.ofInitialImport(location);
}
2.3.3 创建配置数据环境贡献者集合对象
protected ConfigDataEnvironmentContributors createContributors(
List<ConfigDataEnvironmentContributor> contributors) {
// 调用重载构造方法
return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors);
}
ConfigDataEnvironmentContributors(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
List<ConfigDataEnvironmentContributor> contributors) {
this.logger = logFactory.getLog(getClass());
this.bootstrapContext = bootstrapContext;
// 创建一个根贡献者,根贡献者包含所有的贡献者
this.root = ConfigDataEnvironmentContributor.of(contributors);
}
static ConfigDataEnvironmentContributor of(List<ConfigDataEnvironmentContributor> contributors) {
// 以ImportPhase.BEFORE_PROFILE_ACTIVATION为key,将之前获取到的,已存在贡献者,初始导入贡献者 的集合作为value,
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children = new LinkedHashMap<>();
children.put(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.unmodifiableList(contributors));
// 创建一个根贡献者
return new ConfigDataEnvironmentContributor(Kind.ROOT, null, null, false, null, null, null, null, children);
}
创建一个根贡献者,根贡献者初始化只有自己的角色,以及children属性
ConfigDataEnvironmentContributor(Kind kind, ConfigDataLocation location, ConfigDataResource resource,
boolean fromProfileSpecificImport, PropertySource<?> propertySource,
ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties,
ConfigData.Options configDataOptions, Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
this.kind = kind;
this.location = location;
this.resource = resource;
this.fromProfileSpecificImport = fromProfileSpecificImport;
this.properties = properties;
this.propertySource = propertySource;
this.configurationPropertySource = configurationPropertySource;
this.configDataOptions = (configDataOptions != null) ? configDataOptions : ConfigData.Options.NONE;
this.children = (children != null) ? children : Collections.emptyMap();
}
三、执行ConfigDataEnvironment
对象中的procesAndApply
方法
之前我在读源码时自己是大概看明白了,过程很复杂,但是一直不知道怎么去解释,直到我看了这篇文章(SpringBoot外部化配置相关源码API剖析和扩展)[https://www.codenong.com/jsb307d1a5c1bb/]自己觉得比较好,拾人牙慧,借鉴一下
procesAndApply
方法就是把前面的贡献者ConfigDataEnvironmentContributor
中配置,转换为 PropertySource
,并且应用到环境中的过程
整个解析分为三个阶段
- 无Profiles无CloudPlatform阶段
- CloudPlatform解析阶段,根据环境参数spring.main.cloud-platform或者环境变量参数来自动探测云计算厂商环境
- Profiles解析阶段,也就是解析配置了spring.profiles.active,spring.profiles.group 等配置的阶段, 将applicaton-{profile}.properties形式的配置文件中的配置解析出来
每个阶段的解析步骤也分为三步 - 将配置数据位置信息
ConfigDataLocation
解析为配置数据资源返回结果ConfigDataResolutionResult
,ConfigDataResolutionResult
包含配置数据位置信息ConfigDataLocation
和配置数据资源ConfigDataResource
- 加载配置资源返回结果
ConfigDataResolutionResult
中的ConfigDataResource
,解析成ConfigData
,ConfigData
中包含一个PropertySource
- 再将新解析出的
PropertySource
转变为贡献者,然后替换原有的贡献者
当三个阶段都解析完成后,就会将贡献者中的PropertySource
,加入到环境中
说完上面这些理论性的东西,我们再看时解析之前,先看看我的配置文件,以及启动参数
启动参数spring.profiles.active=test
,主配置文件中配置了spring.profiles.group.test=testdb,testmq
,所以application-test.properties,application-testdb.properties,application-testmq.properties三个配置文件会激活
启动参数spring.config.additional-location=file:./app-custom/custom.properties
,所以app-custom目录下custom.properties会激活
启动参数spring.config.import=file:./app-import/
,所以app-import目录下的application.properties会激活,
app-import目录下的application.properties中配置了spring.config.import=file:./app-import/other.properties
,所以app-import目录下的other.properties也会激活
开始debug,先看一下此时的root贡献者
void processAndApply() {
// 创建一个数据导入器,数据导入器专门用于解析贡献者中的路径,到具体解析时在看, 实例化过程就不看了
ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers, this.loaders);
registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);
// 第一阶段,解析初始导入贡献者,见3.1
ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
// 创建一个配置环境激活上下文,激活上下文和云平台以及profiles的关系,此时创建的只推断云平台,因为我们是本地debug,没有云平台相关配置,其中的cloudPlatform属性为空
ConfigDataActivationContext activationContext = createActivationContext(contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
// 第二阶段,和云平台相关配置,由于上面解析cloudPlatform为null,所以贡献者没有任何变化,我们也不用看,哪怕有云配置相关配置,解析步骤也和第一阶段一样,我们只需要看3.1
contributors = processWithoutProfiles(contributors, importer, activationContext);
// 对配置环境激活上下文进行处理,这次从环境中获取了了profiles相关的配置
activationContext = withProfiles(contributors, activationContext);
// 第三阶段,解析启动参数中的 spring.profiles.active 以及,主配置文件中的 spring.profiles.group,解析流和第三阶段一样,见3.1
contributors = processWithProfiles(contributors, importer, activationContext);
// 所有的贡献者都被解析出来,并且每个贡献者的属性源也被解析出来,该方法就是将贡献者中的属性源,添加到环境中,见3.2
applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(),importer.getOptionalLocations());
}
3.1、解析配置,三个阶段的入口
三个阶段入口大同小异,最终都是调用了,贡献者集合对象的withProcessedImports
方法
// 第一阶段入口,无activationContext,其实就是无云平台,无profiles
private ConfigDataEnvironmentContributors processInitial(ConfigDataEnvironmentContributors contributors,
ConfigDataImporter importer) {
this.logger.trace("Processing initial config data environment contributors without activation context");
contributors = contributors.withProcessedImports(importer, null);
registerBootstrapBinder(contributors, null, DENY_INACTIVE_BINDING);
return contributors;
}
// 第二阶段入口,如果配置了云平台,此时就会进行云平台解析,但是我们这次没有
private ConfigDataEnvironmentContributors processWithoutProfiles(ConfigDataEnvironmentContributors contributors,
ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
this.logger.trace("Processing config data environment contributors with initial activation context");
contributors = contributors.withProcessedImports(importer, activationContext);
registerBootstrapBinder(contributors, activationContext, DENY_INACTIVE_BINDING);
return contributors;
}
// 第三阶段入口,如果配置了 spring.profile.active和spring.profile.groups 则会进行响应解析,我们上文中介绍到有配置
private ConfigDataEnvironmentContributors processWithProfiles(ConfigDataEnvironmentContributors contributors,
ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
this.logger.trace("Processing config data environment contributors with profile activation context");
contributors = contributors.withProcessedImports(importer, activationContext);
registerBootstrapBinder(contributors, activationContext, ALLOW_INACTIVE_BINDING);
return contributors;
}
debug进入withProcessedImports
方法
ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer,
ConfigDataActivationContext activationContext) {
// 第一第二阶段解析是值为BEFORE_PROFILE_ACTIVATION,第三阶段解析时值为AFTER_PROFILE_ACTIVATION
ImportPhase importPhase = ImportPhase.get(activationContext);
this.logger.trace(LogMessage.format("Processing imports for phase %s. %s", importPhase,
(activationContext != null) ? activationContext : "no activation context"));
ConfigDataEnvironmentContributors result = this;
int processed = 0;
while (true) {
// 获取一个需要处理的贡献者,需要处理的贡献者要满足下面两个条件中的任意一个条件
// 1.贡献者的角色为UNBOUND_IMPORT
// 2.children属性集合中没有importPhase这个key,并且满足下面4个条件中任意一个条件
// 2.1、properties为null
// 2.2、properties不为null,但是properties的activate属性为null
// 2.3、properties不为null,properties的activate属性也不为null,但是传入的activationContext为null
// 2.4、properties,properties的activate,传入的activationContext都不为为null,并且满足下面两个条件
// 2.4.1、properties中的onCloudPlatform为null or properties中的onCloudPlatform不为null并且和activationContext中onCloudPlatform相同
// 2.4.2、properties中的onProfile为null or properties中的onProfile不为null并且和activationContext中的profiles匹配
// 第二个条件的含义简单说就是,贡献者有properties中指示了有要解析的配置文件路径,但是children中发现并没有对应的解析配置,所以就需要解析
ConfigDataEnvironmentContributor contributor = getNextToProcess(result, activationContext, importPhase);
if (contributor == null) {
this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processed));
return result;
}
// 角色为UNBOUND_IMPORT的贡献者进行解析
if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
Iterable<ConfigurationPropertySource> sources = Collections
.singleton(contributor.getConfigurationPropertySource());
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
result, activationContext, true);
// 配置文件中占位符的替换
Binder binder = new Binder(sources, placeholdersResolver, null, null, null);
// 配置文件中的spring.config.import之类的配置解析
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
// 将解析后的贡献者替换原有贡献者
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, bound));
continue;
}
// 满足第二个条件的贡献者就在这里解析
// 创建路径解析上下文
ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(
result, contributor, activationContext);
// 创建配置数据加载上下文
ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
// 获取要解析的配置数据路径
List<ConfigDataLocation> imports = contributor.getImports();
this.logger.trace(LogMessage.format("Processing imports %s", imports));
// 上文提到的配置数据导入器,就在此处用到,将路径解析上下文,配置数据加载上下文,要加载的配置数据路径全部传入,这也是解析的核心步骤,包含了解析三大步的两大步,也是接下来的debug的地方
// 解析的结果就是一个map,key为ConfigDataResolutionResult包含了location和resource,value为ConfigData,包含了PropertiesSource
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
locationResolverContext, loaderContext, imports);
this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));
// 后面的流程就是解析的第三大步
// 创建一个新的贡献者,这个贡献者在importPhase阶段的子贡献者就是解析出的数据
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
asContributors(imported));
// 替换root贡献者中的当前解析贡献者,为上面的新的已被解析的贡献者,
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, contributorAndChildren));
processed++;
}
}
debug进入ConfigDataImporter
的resolveAndLoad
方法
Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
List<ConfigDataLocation> locations) {
try {
// 获取profiles,第一阶段为空,在第三阶段时如果配置了,则有值
Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
// 解析的第一大步,解析位置路径,见3.1.1
List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);
// 解析的第二大步,加载位置路径中的配置,见3.1.2
return load(loaderContext, resolved);
}
catch (IOException ex) {
throw new IllegalStateException("IO error on loading imports from " + locations, ex);
}
}
3.1.1、解析位置路径
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
Profiles profiles, List<ConfigDataLocation> locations) {
// 收集配置数据路径解析返回
List<ConfigDataResolutionResult> resolved = new ArrayList<>(locations.size());
// 遍历要解析的配置文件路径
for (ConfigDataLocation location : locations) {
// 调用重载的解析方法
resolved.addAll(resolve(locationResolverContext, profiles, location));
}
return Collections.unmodifiableList(resolved);
}
继续debug,发现调用了解析器结合对象的解析方法
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
Profiles profiles, ConfigDataLocation location) {
try {
//ConfigDataImporter中的解析器集合对象
return this.resolvers.resolve(locationResolverContext, location, profiles);
}
catch (ConfigDataNotFoundException ex) {
handle(ex, location, null);
return Collections.emptyList();
}
}
debug进入解析器集合对象ConfigDataLocationResolvers
的resolve
方法
List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location,
Profiles profiles) {
if (location == null) {
return Collections.emptyList();
}
// 遍历解析器,寻找一个能够解析当前路径的解析器,一般我们获取的都是StandardConfigDataLocationResolver解析器
for (ConfigDataLocationResolver<?> resolver : getResolvers()) {
if (resolver.isResolvable(context, location)) {
// 继续调用解析器集合对象中的重载方法,这一次传入了一个解析器
return resolve(resolver, context, location, profiles);
}
}
throw new UnsupportedConfigDataLocationException(location);
}
debug进入重载的解析方法
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolver<?> resolver,
ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) {
// 解析不带profile的配置文件,也就是application为名称的主配置文件,我们一会debug这个方法
List<ConfigDataResolutionResult> resolved = resolve(location, false, () -> resolver.resolve(context, location));
// 如果主配置文件都不存在,就不必解析带有profile的配置文件了
if (profiles == null) {
return resolved;
}
// 解析带有profile的配置文件,类似于 applicaiton-{profile}.[extend],解析流和上面一模一样
List<ConfigDataResolutionResult> profileSpecific = resolve(location, true,
() -> resolver.resolveProfileSpecific(context, location, profiles));
// 将两个结果合并为一个集合
return merge(resolved, profileSpecific);
}
我们只需要看不带profile的配置文件解析过程就行,和带profile的配置文件解析过程一样,就是多在后面拼接了一个profile
private List<ConfigDataResolutionResult> resolve(ConfigDataLocation location, boolean profileSpecific,
Supplier<List<? extends ConfigDataResource>> resolveAction) {
// 调用上面传入的一个lambda表达式,看看结果是否为空,为空,创建一个空的返回结果,我们接下来debu这个lambda表达式
List<ConfigDataResource> resources = nonNullList(resolveAction.get());
// 收集结果
List<ConfigDataResolutionResult> resolved = new ArrayList<>(resources.size());
for (ConfigDataResource resource : resources) {
// 将解析结果转换为ConfigDataResolutionResult,包含了路径信息,资源信息,以及是否为带有profile的配置
resolved.add(new ConfigDataResolutionResult(location, resource, profileSpecific));
}
return resolved;
}
可见lambda表达式中再次调用重载,分类两步
一步将路径ConfigDataLocation
解析为标准配置数据引用 StandardConfigDataReference
一步将标准配置数据引用对象StandardConfigDataReference
解析为标准数据资源对象StandardConfigDataResource
public List<StandardConfigDataResource> resolve(ConfigDataLocationResolverContext context,
ConfigDataLocation location) throws ConfigDataNotFoundException {
// 有的location中的路径,其实是包含了多个路径,比如默认的路径,所以在这里做一次拆分,分割为多个location
//将路径`ConfigDataLocation`解析为 `StandardConfigDataReference`见3.1.1.1
//将标准配置数据引用对象`StandardConfigDataReference`解析为标准数据资源对象`StandardConfigDataResource`见3.1.1.2
return resolve(getReferences(context, location.split()));
}
3.1.1.1、将路径ConfigDataLocation
解析为标准配置数据引用 StandardConfigDataReference
private Set<StandardConfigDataReference> getReferences(ConfigDataLocationResolverContext context, ConfigDataLocation[] configDataLocations) {
// 收集标准配置数据引用集合
Set<StandardConfigDataReference> references = new LinkedHashSet<>();
// 遍历配置数据路径
for (ConfigDataLocation configDataLocation : configDataLocations) {
// 将路径`ConfigDataLocation`解析为标准配置数据引用 `StandardConfigDataReference
references.addAll(getReferences(context, configDataLocation));
}
return references;
}
debug进入上面的getReferences
private Set<StandardConfigDataReference> getReferences(ConfigDataLocationResolverContext context, ConfigDataLocation configDataLocation) {
//获取配置数据位置中的资源路径
String resourceLocation = getResourceLocation(context, configDataLocation);
try {
//判断该位置是否为目录
if (isDirectory(resourceLocation)) {
//如果是目录则默认获取名字为application,扩展为.yaml,.yml,.xml,.properties,的配置文件,我们接下来debug这个方法
//第三阶段带profile的配置文件解析也会调用该方法,不过最后一个参数就不是固定的 NO_PROFILE 常量了,而是传入的profile
return getReferencesForDirectory(configDataLocation, resourceLocation, NO_PROFILE);
}
// 如果不是目录,则当做文件进行解析,会检查扩展是否为.yaml,.yml,.xml,.properties,如果不是则报错,过程我们也不看了
return getReferencesForFile(configDataLocation, resourceLocation, NO_PROFILE);
}
catch (RuntimeException ex) {
throw new IllegalStateException("Unable to load config data from '" + configDataLocation + "'", ex);
}
}
debug进入上面的getReferencesForDirectory
方法
该方法无profile的解析调用,profile参数为null
有profile的解析调用,就是传入的profile
private Set<StandardConfigDataReference> getReferencesForDirectory(ConfigDataLocation configDataLocation,
String directory, String profile) {
Set<StandardConfigDataReference> references = new LinkedHashSet<>();
// 遍历默认的配置文件名字,这里只有一个就是 application
for (String name : this.configNames) {
// 真正解析的位置,以及确定了配置文件的名字,位置,目录,profile
Deque<StandardConfigDataReference> referencesForName = getReferencesForConfigName(name, configDataLocation, directory, profile);
references.addAll(referencesForName);
}
return references;
}
debug进入getReferencesForConfigName
方法
private Deque<StandardConfigDataReference> getReferencesForConfigName(String name, ConfigDataLocation configDataLocation, String directory, String profile) {
Deque<StandardConfigDataReference> references = new ArrayDeque<>();
// 遍历属性源加载器,属性源加载器是在路径解析器创建是,构造方法中从spring.factory中读取到并实例化的,有两个
// PropertiesPropertySourceLoader,解析扩展名为 .properties/.xml的配置文件
// YamlPropertySourceLoader,解析扩展名为 .yaml/.yml 的配置文件
for (PropertySourceLoader propertySourceLoader : this.propertySourceLoaders) {
// 遍历属性源加载器的扩展
for (String extension : propertySourceLoader.getFileExtensions()) {
// 创建StandardConfigDataReference,这里面就有配置数据位置,目录,绝对位置,profile,扩展,以及对应的属性源解析器
StandardConfigDataReference reference = new StandardConfigDataReference(configDataLocation, directory,
directory + name, profile, extension, propertySourceLoader);
if (!references.contains(reference)) {
references.addFirst(reference);
}
}
}
// 解析完成后,里面一般有4个 标准配置数据引用
return references;
}
3.1.1.2、标准配置数据引用对象StandardConfigDataReference
解析为标准数据资源对象StandardConfigDataResource
在上面一个配置数据位置对象,会解析成4个标准配置数据引用对象,这里就要根据引用对象,解析为资源对象,
private List<StandardConfigDataResource> resolve(Set<StandardConfigDataReference> references) {
List<StandardConfigDataResource> resolved = new ArrayList<>();
// 遍历标准配置数据引用对象进行解析
for (StandardConfigDataReference reference : references) {
// 接下来要debug的方法
resolved.addAll(resolve(reference));
}
if (resolved.isEmpty()) {
resolved.addAll(resolveEmptyDirectories(references));
}
return resolved;
}
debug上面的要进入的resolve
方法
private List<StandardConfigDataResource> resolve(StandardConfigDataReference reference) {
// 判断位置路径中是否有通配符*
if (!this.resourceLoader.isPattern(reference.getResourceLocation())) {
// 一般我们没有,就要进入这个方法,接下来我们进入这个方法
return resolveNonPattern(reference);
}
// 有通配符就可能有多个文件资源,在这个方法
return resolvePattern(reference);
}
debug进入resolveNonPattern
这个方法
private List<StandardConfigDataResource> resolveNonPattern(StandardConfigDataReference reference) {
// 先通过资源加载器获取资源
Resource resource = this.resourceLoader.getResource(reference.getResourceLocation());
// 潘德这个资源是否存在,以及是否可跳过
if (!resource.exists() && reference.isSkippable()) {
// 如果资源不存在,且可跳过,就创建一个空的配置数据资源对象
logSkippingResource(reference);
return Collections.emptyList();
}
// 存在就创建一个标准配置数据资源,里面包含 资源 以及 标准配置数据引用对象
return Collections.singletonList(createConfigResourceLocation(reference, resource));
}
3.1.2、加载位置路径中的配置
在经过配置数据配置解析后的记过为ConfigDataResolutionResult集合,ConfigDataResolutionResult中包含了配置数据位置,以及配置数据资源
将资源中的配置解析出,就在这一步
private Map<ConfigDataResolutionResult, ConfigData> load(ConfigDataLoaderContext loaderContext,
List<ConfigDataResolutionResult> candidates) throws IOException {
Map<ConfigDataResolutionResult, ConfigData> result = new LinkedHashMap<>();
// 遍历配置数据结果
for (int i = candidates.size() - 1; i >= 0; i--) {
// 获取配置数据结果
ConfigDataResolutionResult candidate = candidates.get(i);
// 获取配置数据位置
ConfigDataLocation location = candidate.getLocation();
// 获取配置数据资源
ConfigDataResource resource = candidate.getResource();
// 检查这个资源是否可选加入缓存
if (resource.isOptional()) {
this.optionalLocations.add(location);
}
// 检查这个资源是否已经加载过,加入已经加载过的路径
if (this.loaded.contains(resource)) {
this.loadedLocations.add(location);
}
else {
try {
// 调用加载器集合对象对资源进行加载,返回的ConfigData对象中有PropertiesSource对象,也是本方法的核心,我们要debug的位置
ConfigData loaded = this.loaders.load(loaderContext, resource);
if (loaded != null) {
// 加载的结果不为null,则存入已加载资源缓存,已加载路径缓存
this.loaded.add(resource);
this.loadedLocations.add(location);
// 存入返回的记过,key为配置数据结果,value为加载结果
result.put(candidate, loaded);
}
}
catch (ConfigDataNotFoundException ex) {
handle(ex, location, resource);
}
}
}
return Collections.unmodifiableMap(result);
}
debug进入ConfigDataLoaders
的load
方法
<R extends ConfigDataResource> ConfigData load(ConfigDataLoaderContext context, R resource) throws IOException {
// 获取一个合适 加载器,一般都是StandardConfigDataLoader
ConfigDataLoader<R> loader = getLoader(context, resource);
this.logger.trace(LogMessage.of(() -> "Loading " + resource + " using loader " + loader.getClass().getName()));
// 然后我们用StandardConfigDataLoader加载器加载资源
return loader.load(context, resource);
}
debug进入StandardConfigDataLoader
的load
方法
public ConfigData load(ConfigDataLoaderContext context, StandardConfigDataResource resource)
throws IOException, ConfigDataNotFoundException {
// 如果资源是一个空的目录,则返回一个空的配置数据
if (resource.isEmptyDirectory()) {
return ConfigData.EMPTY;
}
// 资源不存在则报错
ConfigDataResourceNotFoundException.throwIfDoesNotExist(resource, resource.getResource());
// 获取资源中的 标准配置数据引用
StandardConfigDataReference reference = resource.getReference();
// 将资源转为OriginTrackedWritableResource类型
Resource originTrackedResource = OriginTrackedResource.of(resource.getResource(),
Origin.from(reference.getConfigDataLocation()));
// 格式化资源的名字
String name = String.format("Config resource '%s' via location '%s'", resource,
reference.getConfigDataLocation());
// 通过标准配置数据引用里面的 属性源解析器 来加载资源,返回的结果就是 PropertySource
List<PropertySource<?>> propertySources = reference.getPropertySourceLoader().load(name, originTrackedResource);
PropertySourceOptions options = (resource.getProfile() != null) ? PROFILE_SPECIFIC : NON_PROFILE_SPECIFIC;
// 然后创建一个配置数据对象,传入属性源
return new ConfigData(propertySources, options);
}
3.2、将属性源应用到环境中
private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations,
Set<ConfigDataLocation> optionalLocations) {
// 检查一些错误的配置,比如spring.profile.active,不能出现在非主配置文件中
checkForInvalidProperties(contributors);
// 检查非默认位置的 配置文件是否都加载过
checkMandatoryLocations(contributors, activationContext, loadedLocations, optionalLocations);
// 获取环境中的 属性源集合对象
MutablePropertySources propertySources = this.environment.getPropertySources();
// 将贡献者中的属性源都添加到环境的的属性源集合对象,也是核销方法
applyContributor(contributors, activationContext, propertySources);
// 将默认的属性源移动到最后面
DefaultPropertiesPropertySource.moveToEnd(propertySources);
// 获取profile
Profiles profiles = activationContext.getProfiles();
this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault()));
// 设置环境中的默认profiles
this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));
this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));
// 设置环境中的 激活的profiles
this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));
// 发送环境更新事件,其实前面我们写到过,没有这个属性为空
this.environmentUpdateListener.onSetProfiles(profiles);
}
debug 进入 applyContributor
private void applyContributor(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext, MutablePropertySources propertySources) {
this.logger.trace("Applying config data environment contributions");
// 遍历贡献者
for (ConfigDataEnvironmentContributor contributor : contributors) {
PropertySource<?> propertySource = contributor.getPropertySource();
// 将角色为BOUND_IMPORT并且属性源不为空的贡献者的属性源加入 属性源集合对象中
if (contributor.getKind() == ConfigDataEnvironmentContributor.Kind.BOUND_IMPORT && propertySource != null) {
if (!contributor.isActive(activationContext)) {
this.logger.trace(
LogMessage.format("Skipping inactive property source '%s'", propertySource.getName()));
}
else {
this.logger
.trace(LogMessage.format("Adding imported property source '%s'", propertySource.getName()));
propertySources.addLast(propertySource);
this.environmentUpdateListener.onPropertySourceAdded(propertySource, contributor.getLocation(),
contributor.getResource());
}
}
}
}
最后我们看看解析前和解析后环境中的PropertiesSource
对比
很明显,我们之前配置的那些配置文件都已经在里面了