SpringBoot 版本 : 2.2.1.RELEASE
Spring 版本 : 5.2.1.RELEASE
关键词:ConfigFileApplicationListener
,EnvironmentPostProcessor
,SprngBoot启动顺序源码解读
注:本文通过源码指出 Spring & SpringBoot 相关配置加载顺序,如有异议,欢迎下方评论
- 相信用SpringBoot框架做开发的时候,很多人脑海中会浮现这样一个问题:框架内得这么多配置都是什么时候加载得呢?顺序又是怎样的呢? 带着这样得问题,本篇将会给出具体得相关配置源码所在得位置。
- 👍了解了SpringBoot启动顺序,本篇阅读起来会更加流畅👍。推荐文集:
SpringBoot启动 源码深度解析(一)
SpringBoot启动 源码深度解析(二)
SpringBoot启动 源码深度解析(三)
SpringBoot启动 源码深度解析(四)
1. 加载SpringBoot项目默认配置文件
1.1 参考Spring官网 章节 2.3. Application Property Files
优先级从高到低:
- 项目根目录下 config 目录下得配置文件
- 项目根目录下得配置文件
- resources下面cofnig目录下得配置文件
- resources下面得配置文件
总结:
1. 同一目录级别下,带有config目录得优先级高于不带有config目录得配置
2. 项目根目录下配置属性得优先级高于resources目录
3. 同级目录下properties优先于yml文件
-
源码位置:
2. 外部化配置(重点)
2.1 参考Spring官网 章节 2. Externalized Configuration
- Devtools主目录得全局设置属性(前提是要激活Devtools)
- 带有@TestPropertySource得注解配置
- 带有@SpringBootTest上注解配置
- 命令行参数:SpringBoot启动时,通过 org.springframework.boot.SpringApplication#configurePropertySources(ConfigurableEnvironment environment, String[] args)设置得命令行参数
,然后会向属性中添加资源SimpleCommandLinePropertySource对象,而该对象得构造中,会调用org.springframework.core.env.SimpleCommandLineArgsParser#parse方法解析命令行参数
👍这不正是我们服务启动时( java -jar --server.port=8080)经常用到得命令格式吗👍- 嵌入在环境得Json属性:此处有个很重要得监听器
ConfigFileApplicationListener
,该监听器会监听两个事件:ApplicationEnvironmentPreparedEvent
与ApplicationPreparedEvent
,而ApplicationEnvironmentPreparedEvent事件
会在准备好环境之后(见图6
)调用代码listeners.environmentPrepared(environment)发出.此时ConfigFileApplicationListener
就会监听到事件,执行onApplicationEvent回调监听
,首先从spring.properties中加载EnvironmentPostProcessor实例
并调用实例得postProcessEnvironment方法,
此时我们发现其中有个实例为SpringApplicationJsonEnvironmentPostProcessor
,进到类中:
会在postProcessEnvironment
方法中处理环境中得属性spring.application.json(优先)
与SPRING_APPLICATION_JSON
,若不为空,则调用processJson
方法解析json值,,解析之后会调用addJsonPropertySource
方法执行具体得添加操作,首先调用findPropertySource()
👍其实作者一直对官网给得顺序有质疑,之前作者一直认为servletConfigInitParams,servletContextInitParams,jndiProperties这三个属性在json处理之前,但是看到这个findPropertySource方法之后才豁然开朗,如果你也有类时得同感,我认为你对SpringBoot启动顺序以及相关底层源码已经有一定得了解了。👍
- servletConfigInitParams;servletContextInitParams;jndiProperties:这些参数是创建Servlet环境时添加得属性(
重要得事情再说一遍,SpringBoot启动顺序很重要
)
参数顺序设置在StandardServletEnvironment类中。方法中还有调用 super.customizePropertySources(propertySources),执行父类得自定义属性资源添加方法:- 细心得读者会发现
ConfigFileApplicationListener
也是EnvironmentPostProcessor得实例,但是为什么不在spring.factories中呢?我们仔细观察图5
得方法发现有一段代码是postProcessors.add(this)
顿时恍然大悟,此操作会将自己添加到最后一个位置去处理postProcessEnvironment
方法,而ConfigFileApplicationListener
得处理逻辑:标红得两处重点分析:将random属性添加到systemEnvironment之后
- 第二处是Loader类得load方法:首先会调用构造器:
其中几行很重要得代码:首先初始化属性占位符解析器,然后赋值资源加载器,最后在spring.factories中获取PropertySourceLoader实例
:看名字我们大概可以猜到是对properties、yaml文件
得解析:发现还可以解析xml与yml
,所以总共可以解析得文件类型包括properties、xml、yml、yaml
。然后就是load方法实现属性配置得加载:
方法内部调用了FilteredPropertySource得apply方法
:
若环境参数包含当前属性,那么首先获取原始得属性资源,若不为空,新生成一个FilteredPropertySource对象替换掉之前得属性资源对象,然后Comsumer消费掉原始资源(Consumer,函数式编程)。
👍然后lambada里面得逻辑是:
1. 首先调用 initializeProfiles初始化默认得Profile,没有设置环境得话就是默认得 application.properties和application-default.properties,设置了就是application-${profile}.properties得格式
2. 然后While 循环取出Profile进行判断是否是默认得Profile(默认创建得都是false)
3. 接着调用
getSearchLocations方法首先会去查找默认得属性spring.config.location,没有则会去找 spring.config.additional-location属性
若spring.config.additional-location属性也没有,则使用默认得位置 classpath:/,classpath:/config/,file:./,file:./config/(优先级由低到高)
:
先判断环境中是否包含spring.config.name属性,若包含获取对应得属性值解析成Set集(属性可以有占位符,可以通过逗号分隔),若不包含则使用成员变量names作为属性值(可以通过setSearchNames方法设置),默认值为application,也就是说getSearchNames方法是获取得不带Profile得文件名称。
4. 然后执行具体得load加载,循环判断 getSearchNames解析出来得name,(1)若值为空,遍历构造器中解析出来得属性资源加载器 this.propertySourceLoaders,调用canLoadFileExtension方法去判断 getSearchLocations返回得location与任意文件扩展名相同即可(此时配置得只能是文件扩展名,不能为目录),然后执行具体得资源加载
(2)若不为空(正常情况下都不会为空),同样遍历 this.propertySourceLoaders,获取支持得扩展名,调用 loadForFileExtension方法补全配置文件名并加载具体得属性配置
首先构造默认得配置(不含有Profile得,例如:application.properties),再构造一个带有Profile得配置。判断Profile若不为空,则拼接完整文件路径调用 load方法加载(此处得load方法就是具体得资源加载了,通过Spring得抽象Resource接口读取资源文件。Profile为空则不拼接Profle到全路径中,同理调用load方法进行资源加载)
- 到此,官网中给出得常用配置顺序基本已经分析完毕,其中还有一块绑定配置也很重要,有时间作者也会整理出相应得源码与大家一起分享与探讨
总结:
1. 👍根据SpringBoot启动时得顺序👍,通过源码找到对应得位置有助于加深印象,对启动源码熟悉得话更有助于读者记忆相关顺序
2. 其次就是源码中使用了函数式编程,大量得lambada表达式,若对lambada不是很了解得同学可以先把这块得基础打好,那么阅读起来源码会更加得心应手
3. 另外,配置依托于环境
,配置得属性相关设置都会保存在环境下面得缓存中
- ☛ 文章要是勘误或者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码获得的知识,难免会有疏忽!
- ☛ 要是感觉文章对你有所帮助,不妨点个关注,或者移驾看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
- ☛ 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处!