Spring Boot 源码分析 - 配置加载
参考 知识星球 中 芋道源码 星球的源码解析,一个活跃度非常高的 Java 技术社群,感兴趣的小伙伴可以加入 芋道源码 星球,一起学习😄
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读
Spring Boot 版本:2.2.x
最好对 Spring 源码有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章导读》 系列文章
如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~
该系列其他文章请查看:《精尽 Spring Boot 源码分析 - 文章导读》
概述
在我们的 Spring Boot 应用中,可以很方便的在 application.yml
或 application.properties
文件中添加需要的配置信息,并应用于当前应用。那么,对于 Spring Boot 是如何加载配置文件,如何按需使指定环境的配置生效的呢?接下来,我们一起来看看 Spring Boot 是如何加载配置文件的。
提示:Spring Boot 加载配置文件的过程有点绕,本篇文章有点长,可选择性的跳过 Loader 这一小节
回顾
回到前面的 《SpringApplication 启动类的启动过程》 这篇文章,Spring Boot 启动应用的入口和主流程都是在 SpringApplication#run(String.. args)
方法中。
在这篇文章的 6. prepareEnvironment 方法 小节中可以讲到,会对所有的 SpringApplicationRunListener
广播 应用环境已准备好 的事件,如下:
只有一个 EventPublishingRunListener
事件发布器,里面有一个事件广播器,封装了几个 ApplicationListener 事件监听器,如下:
其中有一个 ConfigFileApplicationListener
对象,监听到上面这个事件,会去解析 application.yml
等应用配置文件的配置信息
在 Spring Cloud 还会配置一个 BootstrapApplicationListener
对象,监听到上面的这个事件会创建一个 ApplicationContext 作为当前 Spring 应用上下文的父容器,同时会读取 bootstrap.yml
文件的信息
ConfigFileApplicationListener
org.springframework.boot.context.config.ConfigFileApplicationListener
,Spring Boot 的事件监听器,主要用于加载配置文件到 Spring 应用中
相关属性
属性不多,几个关键的属性都有注释,同时支持处理的事件有 ApplicationEnvironmentPreparedEvent 和 ApplicationPreparedEvent
我们看到它实现了 EnvironmentPostProcessor 这个接口,用于对 Environment 进行后置处理,在刷新 Spring 应用上下文之前
1. onApplicationEvent 方法
onApplicationEvent(ApplicationEvent)
方法,ApplicationListener 处理事件的方法,如下:
我们在前面 回顾 中讲到,会广播一个 应用环境已准备好 的事件,也就是 ApplicationEnvironmentPreparedEvent 事件
处理该事件的过程如下:
-
通过类加载器从
META-INF/spring.factories
文件中获取EnvironmentPostProcessor
类型的类名称,并进行实例化 -
当前对象也是
EnvironmentPostProcessor
实现类,添加进去 -
将这些
EnvironmentPostProcessor
进行排序 -
遍历这些
EnvironmentPostProcessor
依次对 Environment 进行处理- 依次对当前 Environment 进行处理,上面第
2
步添加了当前对象,我们直接看到当前类的这个方法
- 依次对当前 Environment 进行处理,上面第
2. postProcessEnvironment 方法
postProcessEnvironment(ConfigurableEnvironment, SpringApplication)
方法,实现 EnvironmentPostProcessor
接口的方法,对 Environment 进行后置处理
直接调用 addPropertySources(..)
方法,为当前 Spring 应用的 Environment 环境对象添加属性(包括 application.yml
配置文件的解析)
过程如下:
-
往 Spring 应用的 Environment 环境对象添加随机值的
RandomValuePropertySource
属性源,这样就可直接通过@Value(random.uuid)
随机获取一个 UUID逻辑很简单,感兴趣的可以去看看
-
创建一个 Loader 对象,设置占位符处理器,资源加载器,PropertySourceLoader 配置文件加载器
-
调用这个 Loader 的
load()
方法,加载配置信息,并放入 Environment 环境对象中
加载配置信息的过程有点绕,嵌套有点深,你可以先理解为,将你的 Spring Boot 或者 Spring Cloud 的配置文件加载到 Environment 中,并激活对应的环境
Loader
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader
,私有内部类,配置文件的加载器
构造方法
属性不多,上面都已经注释了,在构造器中会通过 ClassLoader 从所有的 META-INF/spring.factories
文件中加载出 PropertySourceLoader,如下:
PropertiesPropertySourceLoader:加载 properties
和 xml
文件
YamlPropertySourceLoader:加载 yml
和 yaml
文件
3. load 方法
load()
方法,加载配置信息,并放入 Environment 环境对象中,如下:
方法内部借助 FilteredPropertySource 执行入参中的这个 Consumer 函数,目的就是获取 defaultProperties
默认值的 PropertySource,通常我们没有设置,所以为空对象,如下:
所以我们直接来看到 load()
方法中的 Consumer 函数,整个处理过程如下:
-
调用
initializeProfiles()
方法,初始化 Profile 对象,也就是我们需要加载的 Spring 配置,例如配置的 JVM 变量:dev
、sit
、uat
、prod
-
java -jar xxx.jar --spring.profiles.active=dev
orjava -jar -Dspring.profiles.active=dev xxx.jar
,那么这里的profiles
就会有一个null
和一个dev
-
java -jar xxx.jar
,那么这里的profiles
就会有一个null
和一个default
-
-
依次加载上一步得到的
profiles
对应的配置信息,这里先解析null
对应的配置信息,也就是公共配置针对上面第
1.2
种情况,如果公共配置指定了spring.profiles.active
,那么添加至profiles
中,并移除default
默认 Profile,所以后续和上面第1.1
种情况一样的处理,后面会讲到-
将接下来的准备加载的 Profile 从队列中移除
-
如果不为
null
且不是默认的 Profile,这个方法名不试试取错了??则将其添加至 Environment 的activeProfiles
(有效的配置)中,已存在不会添加也就是保存激活的 Profile 环境
-
调用
load(..)
重载方法,尝试加载配置文件,并解析出配置信息,会根据 Profile 归类,最终保存至this#loaded
集合例如会去加载
classpath:/application.yml
或者classpath:/application-dev.yml
文件,并解析;如果profile
为null
,则会解析出classpath:/application.yml
中的公共配置,因为这里是第一次去加载,所以不需要检查profile
对应的配置信息是否存在 -
将已加载的 Profile 保存
-
-
继续调用
load(..)
重载方法,如果没有指定profile
,那么这里尝试解析所有需要的环境的配置信息,也会根据 Profile 归类,最终保存至this#loaded
集合例如会去加载
classpath:/application.yml
文件并解析出各个 Profile 的配置信息;因为上面可能尝试加载过,所以这里需要检查profile
对应的配置信息是否存在,已存在则不再添加,至于这一步的用途暂时还没搞懂~ -
调用
addLoadedPropertySources()
方法,将上面加载出来的所有配置信息从this#loaded
集合添加至 Environment 中
上面的的 load(..)
重载方法中有一个 Consumer 函数,它的入参又有一个 Consumer 函数,第 2.3
和 3
步的入参不同,注意一下⏩
上面的整个过程有点绕,有点难懂,建议各位小伙伴自己调试代码⏩
3.1 initializeProfiles 方法
initializeProfiles()
方法,初始化 Profile 对象,也就是我们需要加载的 Spring 配置,如下:
过程如下:
-
先往
profile
集合添加一个空的 Profile -
从 Environment 中获取
spring.profiles.active
配置,此时还没有加载配置文件,所以这里获取到的就是你启动jar
包时设置的 JVM 变量,例如-Dspring.profiles.active
,或者启动jar
包时添加的启动参数,例如--spring.profiles.active=dev
在前面的 《SpringApplication 启动类的启动过程》 这篇文章的 6. prepareEnvironment 方法 小节的第
2
步讲过 -
从 Environment 中获取
spring.profiles.include
配置 -
从 Environment 配置的需要激活的 Profile 们,不在上面两个范围内则属于其他
-
将上面找到的所有 Profile 都添加至
profiles
中(通常我们只在上面的第2
步可能有返回结果) -
如果只有一个 Profile,也就是第
1
步添加的一个空对象,那么这里再创建一个默认的
3.2 load 重载方法1
load(Profile, DocumentFilterFactory, DocumentConsumer)
方法,加载指定 Profile 的配置信息,如果为空则解析出公共的配置
过程如下:
-
调用
getSearchLocations()
方法,获取classpath:/
、classpath:/config/
、file:./
、file:./config/
四个路径 -
然后依次遍历,从该路径下找到对应的配置文件,找到了则通过
consumer
进行解析,并添加至loaded
中-
判断是否是文件夹,这里好像都是
-
是文件夹的话找到应用配置文件的名称,默认就是
application
名称 -
遍历上一步获取到
names
,默认只有一个application
,那么这里开始解析application
配置文件了,调用的还是一个load(..)
重载方法
-
总结下来就是这里会尝试从 classpath:/
、classpath:/config/
、file:./
、file:./config/
四个文件夹下面解析 application
名称的配置文件
3.3 load 重载方法2
load(String, String, Profile, DocumentFilterFactory, DocumentConsumer)
方法,加载 application
配置文件,加载指定 Profile 的配置信息,如果为空则解析出公共的配置
过程如下:
-
如果没有应用的配置文件名称,则尝试根据
location
进行解析,暂时忽略 -
遍历 PropertySourceLoader 对配置文件进行加载,回到 Loader 的构造方法中,会有
PropertiesPropertySourceLoader
和YamlPropertySourceLoader
两个对象,前者支持properties
和xml
后缀,后者支持yml
和yaml
-
获取 PropertySourceLoader 支持的后缀,然后依次加载对应的配置文件
也就是说四种后缀,加上前面四个文件夹,那么接下来每次 3.load 方法 都会调用十六次
loadForFileExtension(..)
方法
-
3.4 loadForFileExtension 方法
loadForFileExtension(PropertySourceLoader, String, String, Profile, DocumentFilterFactory, DocumentConsumer)
方法,尝试加载 classpath:/application.yml
配置文件,加载指定 Profile 的配置信息,如果为空则解析出公共的配置
过程如下:
-
创建一个默认的 DocumentFilter 过滤器
defaultFilter
-
创建一个指定 Profile 的 DocumentFilter 过滤器
profileFilter
-
如果传入了
profile
,那么尝试加载application-${profile}.yml
对应的配置文件- 获取
profile
对应的名称,例如application-dev.yml
- 又调用
load(..)
重载方法加载3.1
步的配置文件,这里使用defaultFilter
过滤器,找到公共的配置信息 - 又调用
load(..)
重载方法加载3.1
步的配置文件,这里使用profileFilter
过滤器,找到指定profile
的配置信息 - 也尝试从该文件中加载已经加载过的环境所对应的配置,也就是说
dev
的配置信息,也能在其他的application-prod.yml
中读取
- 获取
-
正常逻辑,继续调用
load(..)
重载方法,尝试加载application.yml
文件中对应 Profile 环境的配置,当然,如果 Profile 为空也就加载公共配置
没有什么复杂的逻辑,继续调用重载方法
3.5 load 重载方法3
load(PropertySourceLoader, String, Profile, DocumentFilter,DocumentConsumer)
方法,尝试加载配置文件,加载指定 Profile 的配置信息,如果为空则解析出公共的配置
过程如下:
-
通过资源加载器获取这个文件资源,例如
classpath:/application.yml
-
如果文件资源不存在,那直接返回了
-
否则,如果文件资源的后缀为空,跳过,直接返回
-
调用
loadDocuments(..)
方法,使用 PropertySourceLoader 加载器加载出该文件资源中的所有属性,并将其封装成 Document 对象Document 对象中包含了配置文件的
spring.profiles
和spring.profiles.active
属性,一个文件不是对应一个 Document,因为在一个yml
文件可以通过---
来配置多个环境的配置,这里也就会有多个 Document -
如果没有解析出 Document,表明该文件资源无效,跳过,直接返回
-
通过 DocumentFilter 对
document
进行过滤,过滤出想要的 Profile 对应的 Document例如入参的 Profile 为
dev
那么这里只要dev
对应 Document,如果 Profile 为空,那么找到没有spring.profiles
配置 Document,也就是我们的公共配置- 如果前面还没有激活的 Profile,那么这里尝试将 Document 中的
spring.profiles.active
添加至profiles
中,同时删除default
默认的 Profile
- 如果前面还没有激活的 Profile,那么这里尝试将 Document 中的
-
将需要的 Document 们进行倒序,因为配置在后面优先级越高,所以需要反转一下
-
如果有需要的 Document,借助 Lambda 表达式调用
addToLoaded(..)
方法,将这些 Document 转换成 MutablePropertySources 保存至this#loaded
集合中
逻辑没有很复杂,找到对应的 application.yml
文件资源,解析出所有的配置,找到指定 Profile 对应的配置信息,然后添加到集合中
你要知道的是上面第 4
步得到的 Document 对象,例如 application.yml
中设置 dev
环境激活,有两个 dev
和 prod
不同的配置,那么这里会得到三个 Document 对象
3.6 loadDocuments 方法
loadDocuments(PropertySourceLoader, String, Resource)
方法,从文件资源中加载出 Document 们,如下:
逻辑比较简单,先通过 PropertySourceLoader 加载配置文件,例如 YamlPropertySourceLoader
加载 application.yml
配置文件
然后将加载出来的 PropertySource 属性源对象们一一封装成 Document 对象,同时放入缓存中
YamlPropertySourceLoader
org.springframework.boot.env.YamlPropertySourceLoader
,yml
和 yaml
配置文件的加载器
可以看到,主要就是通过 org.yaml.snakeyaml.Yaml
解析配置文件
3.7 addToLoaded 方法
addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>>, boolean)
方法,将加载出来的配置信息保存起来,如下:
往 loaded
中添加该 Profile 对应的 PropertySource 属性源们
4. addLoadedPropertySources 方法
addLoadedPropertySources()
方法,将前面加载出来的所有 PropertySource 配置信息们添加到 Environment 环境中
过程如下:
- 获取当前 Spring 应用的 Environment 环境中的配置信息
destination
- 将上面已加载的每个 Profile 对应的属性信息放入一个 List 集合中
loaded
- 将
loaded
进行翻转,因为写在后面的环境优先级更高❓❓❓前面不是翻转过一次吗?好吧,暂时忽略 - 遍历
loaded
,将每个 Profile 对应的 PropertySources 属性信息按序添加到 Environment 环境中
5. applyActiveProfiles 方法
applyActiveProfiles(PropertySource<?> defaultProperties)
方法,设置被激活的 Profile 环境
逻辑比较简单,例如我们配置了 spring.profiles.active=dev
,那么这里将设置 Environment 被激活的 Profile 为 dev
总结
本文分析了 Spring Boot 加载 application.yml
配置文件并应用于 Spring 应用的 Environment 环境对象的整个过程,主要是借助于 Spring 的 ApplicationListener 事件监听器机制,在启动 Spring 应用的过程中,准备好 Environment 的时候会广播 应用环境已准备好 事件,然后 ConfigFileApplicationListener
监听到该事件会进行处理。
加载 application
配置文件的整个过程有点绕,嵌套有点深,想深入了解的话查看上面的内容,每个小节都进行了编号。
大致流程就是先加载出 application.yml
文件资源,然后找到需要的 Profile 对应的 PropertySource 属性信息,包括公告配置,最后将这些 PropertySource 应用于 Environment。
__EOF__

本文链接:https://www.cnblogs.com/lifullmoon/p/14957791.html
关于博主:本着学习与分享的目的,将持续不断的进行知识分享。望各位到访看客如有喜欢的文章,可以点击一下“推荐”,若有不同建议或者意见,也请不吝赐教,博主感激不尽。另外,欢迎转载博主的文章,请务必依据文章下方的版权声明转载。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义