盘点 SpringBoot Application配置的

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜

文章合集 : 🎁 juejin.cn/post/694164…

Github : 👉 github.com/black-ant

CASE 备份 : 👉 gitee.com/antblack/ca…

一. 前言

文章目的 :

  • 梳理 Applicaiton 的加载方式
  • 梳理 Profile 的处理

Spring-enviroment.jpg

二 . 扫描的触发

启动的源头任然是SpringApplication#run , 回顾之前的一篇源码 , 在 SpringApplication 中 ,会执行一段代码 :

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

而一切的起点就是那里 , SpringBoot 通过这一句加载所有的环境信息 :

2.1 SpringApplication # prepareEnvironment

在该环节中 , 对 Environment 进行操作的处理 , 其中包括几个主要的操作 :

  1. ConfigurableEnvironment 的生成
  2. configureEnvironment 细粒度处理
  3. 对 configurationProperties 属性进行处理
  4. 发布 listener 处理执行不同类型配置文件的处理
  5. 将 environment 绑定到 SpringApplication
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码C- SpringApplication # prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 内部通过 WebApplicationType 生成不同的 Environment (可以set 自己的 Environment)
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 重写此方法以完全控制环境自定义,或者重写上述方法之一以分别对属性源或概要文件进行细粒度控制。
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 对 configurationProperties 属性进行处理
ConfigurationPropertySources.attach(environment);
// 发布 listener 处理
listeners.environmentPrepared(environment);
// 将 environment 绑定到 SpringApplication
bindToSpringApplication(environment); -> M1_25
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}

2.1.1 ConfigurableEnvironment 的生成

此处会生成一个 StandardServletEnvironment , 他是 ConfigurableEnvironment 的实现类 , 该对象中存在2个方法 , 分别配置了多个 Source 对象

  • customizePropertySources(MutablePropertySources propertySources)
  • initPropertySources(ServletContext servletContext,ServletConfig servletConfig)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
java复制代码
// Servlet上下文初始化参数属性源名称
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
// Servlet config init parameters属性源名称
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";

// 加载系统属性
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

protected void customizePropertySources(MutablePropertySources propertySources) {

// 这2步主要添加2个空对象
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));
}

// 伪代码 , 来自于父类 , 此处用于加载系统属性 -> PRO:0001
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}


// PS : 这里的多个Sources 将会用于优先级处理
SERVLET_CONFIG_PROPERTY_SOURCE_NAME > SERVLET_CONTEXT_PROPERTY_SOURCE_NAME > JNDI_PROPERTY_SOURCE_NAME


// [PRO:0001] 系统属性的获取方式 ?
return (Map) System.getProperties();

[PRO:0001] 系统属性大概样式 >>>
system_config.jpg

其中最主要的就是第 4 步 , 该步骤中扫描不同的配置处理类

补充 : getOrCreateEnvironment 的获取

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}

2.2 发布 listener 处理执行不同类型配置文件的处理

前面主要是获取 SystemSource , 还没有正式开始 , 我们从 prepareEnvironment 第四步开始看 , 来看看后面怎么处理 :

Step 1 : 发布 Listener , 推动 Enviroment 处理

这里不需要深入太多 , 标准的 Listener 发布方式 , 这里主要发布的是 ApplicationEnvironmentPreparedEvent

1
2
3
4
5
6
7
java复制代码C- SpringApplicationRunListeners
void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
// PS : 此处使用的 Listener 为 ApplicationEnvironmentPreparedEvent
listener.environmentPrepared(environment);
}
}

Step 2 :ConfigFileApplicationListener 处理该事件

核心的处理方式就是 EnvironmentPostProcessor 的循环 , 后面所有的操作均在其中完成 :

1
2
3
4
5
6
7
8
9
JAVA复制代码private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
// 可以看到 , 此处还是通过 Processors 进行处理 -> PS:0001
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}

三 . postProcessors 的处理

前面看了 , 通过调用 postProcessor.postProcessEnvironment 来实现不同的环境加载 , 可以看到 , 此处主要有5个 postProcessors :

PS:0001 ConfigFileApplicationListener 中 postProcessors 有哪些

config_process.jpg

1
2
3
4
5
java复制代码- SystemEnvironmentPropertySourceEnvironmentPostProcessor
- SpringApplicationJsonEnvironmentPostProcessor
- CloudFoundryVcapEnvironmentPostProcessor
- ConfigFileApplicationListener(没错 , 他本身也是个 EnvironmentPostProcessor)
- DebugAgentEnvironmentPostProcessor

3.1 SystemEnvironmentPropertySourceEnvironmentPostProcessor

该类主要是对 systemEnvironment 进行处理 , 前面看到了 , 实际上前面已经拿到 SystemSource , 此处是对这些配置的二次处理 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java复制代码public void postProcessEnvironment(
ConfigurableEnvironment environment, SpringApplication application) {
// >>> systemEnvironment
String sourceName = StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
// 从 environment 中获取 systemEnvironment 的所有属性
PropertySource<?> propertySource = environment.getPropertySources().get(sourceName);
if (propertySource != null) {
replacePropertySource(environment, sourceName, propertySource);
}
}

// 此处主要是把 SystemEnvironmentPropertySource 封装成了 OriginAwareSystemEnvironmentPropertySource
private void replacePropertySource(ConfigurableEnvironment environment, String sourceName,
PropertySource<?> propertySource) {
Map<String, Object> originalSource = (Map<String, Object>) propertySource.getSource();
SystemEnvironmentPropertySource source
= new OriginAwareSystemEnvironmentPropertySource(sourceName,originalSource);
environment.getPropertySources().replace(sourceName, source);
}

[Pro] OriginAwareSystemEnvironmentPropertySource 是什么 ?

// OriginAwareSystemEnvironmentPropertySource 是SystemEnvironmentPropertySource 的子类,
// 提供了获取Origin的方法,即返回SystemEnvironmentOrigin对象

SystemEnvironmentOrigin 提供对原始属性名的访问

3.2 SpringApplicationJsonEnvironmentPostProcessor

该类的主要使用形式是在启动 jar 包时使用 , 这种方式的优先级最高:

java -jar xxx.jar --spring.application.json={\"username\":\"ant-black\"}

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
java复制代码// 从 spring.application.json或等价的SPRING_APPLICATION_JSON中解析JSON ,
// 并将其作为映射属性源添加到环境中

public void postProcessEnvironment(
ConfigurableEnvironment environment, SpringApplication application) {
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.stream().map(JsonPropertyValue::get).filter(Objects::nonNull).findFirst()
.ifPresent((v) -> processJson(environment, v));
}

private void processJson(ConfigurableEnvironment environment, JsonPropertyValue propertyValue) {
JsonParser parser = JsonParserFactory.getJsonParser();
// 使用 JsonParser 将 JSON 解析为 Map 集合
Map<String, Object> map = parser.parseMap(propertyValue.getJson());
if (!map.isEmpty()) {
addJsonPropertySource(environment, new JsonPropertySource(propertyValue, flatten(map)));
}
}

// 这里可以看到 , spring.application.json 添加的优先级是最高的
private void addJsonPropertySource(ConfigurableEnvironment environment, PropertySource<?> source) {
MutablePropertySources sources = environment.getPropertySources();
String name = findPropertySource(sources);
if (sources.contains(name)) {
// 添加优先级立即高于命名相对属性源的给定属性源对象
sources.addBefore(name, source);
} else {
sources.addFirst(source);
}
}

3.3 CloudFoundryVcapEnvironmentPostProcessor

一个环境 postprocessor,它知道在现有环境中在哪里找到VCAP(也就是云计算)元数据。它解析VCAP_APPLICATION和VCAP_SERVICES元数据,并将其转储Environment。

这一块比较玄妙 , 首先要知道 VCAP 是什么 :

  • IBM Cloud 中有一个概念 : Cloud Foundry 应用程序
  • 在 Cloud Foundry 运行的应用程序通过存储在一个名为 VCAP services 的环境变量中的凭证获得对绑定服务实例的访问
  • VCAP_APPLICATION 是指云计算的应用单体元数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) {
Properties properties = new Properties();
JsonParser jsonParser = JsonParserFactory.getJsonParser();
addWithPrefix(properties, getPropertiesFromApplication(environment, jsonParser), "vcap.application.");
addWithPrefix(properties, getPropertiesFromServices(environment, jsonParser), "vcap.services.");
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources.contains(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
new PropertiesPropertySource("vcap", properties));
}
else {
propertySources.addFirst(new PropertiesPropertySource("vcap", properties));
}
}
}

简单来说 , 这个类主要是为了云计算的环境准备的 , 因为没有太多涉及 , 此处就不深入了

3.4 ConfigFileApplicationListener

这一块应该就是最核心的一块了 , 这里会对 Application 文件进行扫描处理 :

Step 1 : ConfigFileApplicationListener 的入口

由于 ConfigFileApplicationListener 本身继承了 EnvironmentPostProcessor , 其本身也会加载配置环境 :

1
2
3
4
5
java复制代码// 将配置文件属性源添加到指定的环境
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}

Step 2 : 构建 Loader 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
java复制代码// 来看一看 loader 对象 , 该对象会用于处理属性 Document 并且扫描
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
//
this.environment = environment;
// 解析器 , 用于解析占位符
this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
// 资源加载器
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
// 属性加载器 , 这里是从 SpringFactories 中加载
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
getClass().getClassLoader());
}


// 我们省略其中的一些流程 , 直接关注主流程 :
void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
// 处理 Profile
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
});
}

Step 3 : 扫描所有的路径 , 发起处理

此处通过 getSearchLocations 获得所有的根路径 , 然后依次对根路径下的文件进行扫描

PS :因为处理的顺序不同 , 更靠后的优先级更高

1
2
3
4
5
6
7
8
9
10
java复制代码private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// 获取所有的路径 , 并且对路径进行扫描
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
// 注意 , 这里 SearchName 是获取所有扫描的文件名 , 可以通过 spring.config.name 配置
// 默认名称 application
Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
});
}

这里主要有四个默认路径 :

  • file:./config/
  • file:./
  • classpath:/config/
  • classpath:/

Step 4 : 对路径进行依次处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
throw new IllegalStateException(".....");
}
Set<String> processed = new HashSet<>();
// PropertySourceLoader -> PS:0002
for (PropertySourceLoader loader : this.propertySourceLoaders) {
// 此处获取额外的属性 , 此处为 yml 和 yaml
for (String fileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
// 此处拿到的值为 classpath:/application
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,consumer);
}
}
}
}

PS:0002 PropertySourceLoader 的类型

此处提供了 2 种 PropertySourceLoader ,分别是 PropertiesPropertySourceLoader (.properties)YamlPropertySourceLoader (支持 .yml .yaml’)

Step 5 : loadForFileExtension 处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
if (profile != null) {
// 尝试配置文件中特定配置文件和配置文件段(gh-340)
String profileSpecificFile = prefix + "-" + profile + fileExtension;
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
load(loader, profileSpecificFile, profile, profileFilter, consumer);
// 尝试分析我们已经处理过的文件中的特定部分
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
// Also try the profile-specific section (if any) of the normal file
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}

Step 6 : 对 Application 文件进行读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
java复制代码private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
DocumentConsumer consumer) {
try {
Resource resource = this.resourceLoader.getResource(location);
if (resource == null || !resource.exists()) {
// PS : 当依次对四个路径进行处理的时候 , 如果不存在会由此逻辑返回
return;
}
if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
return;
}
String name = "applicationConfig: [" + location + "]";
// 通过 Document 加载文档 , 此处为 applicationConfig: [classpath:/application.yml]
List<Document> documents = loadDocuments(loader, name, resource);
if (CollectionUtils.isEmpty(documents)) {
// PS : 如果文档为空 ,会从此逻辑返回
return;
}
List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
if (filter.match(document)) {
// 处理 Profiles 文件
addActiveProfiles(document.getActiveProfiles());
addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
loaded.forEach((document) -> consumer.accept(profile, document));
}
}catch (Exception ex) {
throw new IllegalStateException("Failed to load property source from location '" + location + "'", ex);
}
}

Step 7 : 调用 loader 流程发起加载

该逻辑就是对Document 的处理了 , 也不用太深入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码
private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
throws IOException {
DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
List<Document> documents = this.loadDocumentsCache.get(cacheKey);
if (documents == null) {
// 此处 loader 为 org.springframework.boot.env.YamlPropertySourceLoader
List<PropertySource<?>> loaded = loader.load(name, resource);
documents = asDocuments(loaded);
this.loadDocumentsCache.put(cacheKey, documents);
}
return documents;
}


// PS : 此处暂时就可以不用关注了 , 后续主要是Loader 的加载逻辑 , 可以参考之前的文档 :
I- PropertySourceLoader
C- YamlPropertySourceLoader
C- PropertiesPropertySourceLoader

3.5 DebugAgentEnvironmentPostProcessor

作用 : 启用反应堆调试代理

开启 : 调试代理默认是启用的,除非 spring.reactor.debug-agent.enabled 配置属性设置为false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
JAVA复制代码// 这是个特殊的类 , 目的是为了快速加载
private static final String DEBUGAGENT_ENABLED_CONFIG_KEY = "spring.reactor.debug-agent.enabled";


public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (ClassUtils.isPresent(REACTOR_DEBUGAGENT_CLASS, null)) {
Boolean agentEnabled = environment.getProperty(DEBUGAGENT_ENABLED_CONFIG_KEY, Boolean.class);
if (agentEnabled != Boolean.FALSE) {
try {
Class<?> debugAgent = Class.forName(REACTOR_DEBUGAGENT_CLASS);
// 调用init 方法初始化
debugAgent.getMethod("init").invoke(null);
} catch (Exception ex) {
throw new RuntimeException("Failed to init Reactor's debug agent");
}
}
}
}

这个类的主要目的就是为了初始化 REACTOR_DEBUGAGENT_CLASS 类 , 而不是为了加载配置 , 这种方式更加快 (学到了!!!!)

四 . 知识点

4.1 Profile 的处理

Step 1 : 当第一步扫描 Document 的时候 , 会从其中获得 profile 属性 , 并且标注从来

image.png

Step 2 : 处理 Profiles

起因是上文 Step 6 中调用的 addActiveProfilesaddIncludedProfiles

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码void addActiveProfiles(Set<Profile> profiles) {
if (profiles.isEmpty()) {
return;
}
if (this.activatedProfiles) {
return;
}
this.profiles.addAll(profiles);
this.activatedProfiles = true;
removeUnprocessedDefaultProfiles();
}

// 此处设置了 ConfigFileApplicationListener 的 Profiles 属性
private void addIncludedProfiles(Set<Profile> includeProfiles) {
LinkedList<Profile> existingProfiles = new LinkedList<>(this.profiles);
this.profiles.clear();
this.profiles.addAll(includeProfiles);
this.profiles.removeAll(this.processedProfiles);
this.profiles.addAll(existingProfiles);
}

// PS:此处设置完成后 ,Profiles 就会由 default 变成 test

到这里还没完 , 还要做相关的处理 :

Step 3: 反复处理 Profiles

回顾上文 Step 5 : loadForFileExtension 处理 中 , 会发现这样一个处理

1
2
3
4
5
6
7
8
java复制代码private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
// ........ 省略
String profileSpecificFile = prefix + "-" + profile + fileExtension;
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
// 核心 , profile 再次处理
load(loader, profileSpecificFile, profile, profileFilter, consumer);
//.........
}

没错 , 这里就是把 profile传进去原样再扫描一遍 , 而值就是上一步设置的 value

你以为这就完了吗 ? 这就像盗梦空间里面一样 , 你以为你醒了 ,其实还在一个梦里 , 整个逻辑中涉及多个循环处理 , 而 Profile 的循环开启是在第一层

1
2
3
4
5
6
7
8
9
10
11
java复制代码// 当第一遍大循环执行玩抽 , profiles 中就已经有了新的 profiles , 此处会开启第二个大循环
// 此处使用 Deque<Profile> profiles , 以弹出的方式获取对象
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}

补充一下循环体系 :

  • Loader # load : while 循环 profile
  • ConfigFileApplicationListener # load(1) : foreach location
  • ConfigFileApplicationListener # load(1) : foreach names (PS : 这个循环在上个循环的内部)
  • ConfigFileApplicationListener # load(2) : for 循环 propertySourceLoaders
  • ConfigFileApplicationListener # load(2) : for 循环 FileExtensions

load(1) : load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer)

load(2) : load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer)

4.2 具体的加载顺序

Step 1 : 后缀文件的加载顺序

从 ConfigFileApplicationListener # load 方法种 , 我们可以看到 , 加载顺序依次是 :

  • properties
  • xml (没错 , 他会试图加载 xml 文件)
  • yml
  • yaml

总结

本来以为篇幅不会太长, 结构又写了这么多 , 所以将属性的转换放在下一篇将 ,欢迎点赞收藏

修改记录

  • V20210612 : 添加 >> 四 . 知识点
  • V20210804 : 添加结构图

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%