开源框架-SpringCloud系列转载

一篇文章让你看懂SpringBoot配置顺序及底层实现

2020-05-22  本文已影响0人  凡毓不凡

SpringBoot 版本 : 2.2.1.RELEASE
Spring 版本 : 5.2.1.RELEASE
关键词ConfigFileApplicationListenerEnvironmentPostProcessorSprngBoot启动顺序源码解读
注:本文通过源码指出 Spring & SpringBoot 相关配置加载顺序,如有异议,欢迎下方评论

1. 加载SpringBoot项目默认配置文件

1.1 参考Spring官网 章节 2.3. Application Property Files
application.properties(yml)

优先级从高到低:

  1. 项目根目录下 config 目录下得配置文件
  2. 项目根目录下得配置文件
  3. resources下面cofnig目录下得配置文件
  4. resources下面得配置文件

总结:
1. 同一目录级别下,带有config目录得优先级高于不带有config目录得配置
2. 项目根目录下配置属性得优先级高于resources目录
3. 同级目录下properties优先于yml文件

2. 外部化配置(重点)

2.1 参考Spring官网 章节 2. Externalized Configuration
Externalized Configuration
  1. Devtools主目录得全局设置属性(前提是要激活Devtools)
  2. 带有@TestPropertySource得注解配置
  3. 带有@SpringBootTest上注解配置
  4. 命令行参数:SpringBoot启动时,通过 org.springframework.boot.SpringApplication#configurePropertySources(ConfigurableEnvironment environment, String[] args)设置得命令行参数 SpringBoot启动时 configurePropertySources ,然后会向属性中添加资源SimpleCommandLinePropertySource对象,而该对象得构造中,会调用org.springframework.core.env.SimpleCommandLineArgsParser#parse方法解析命令行参数 SimpleCommandLinePropertySource构造器 SimpleCommandLineArgsParser命令行参数解析类
    👍这不正是我们服务启动时( java -jar --server.port=8080)经常用到得命令格式吗👍
  5. 嵌入在环境得Json属性:此处有个很重要得监听器ConfigFileApplicationListener,该监听器会监听两个事件:ApplicationEnvironmentPreparedEventApplicationPreparedEvent,而ApplicationEnvironmentPreparedEvent事件会在准备好环境之后(见图6)调用代码listeners.environmentPrepared(environment)发出.此时ConfigFileApplicationListener就会监听到事件,执行onApplicationEvent回调监听 图5 ,首先从spring.properties中加载EnvironmentPostProcessor实例并调用实例得postProcessEnvironment方法, spring.factories
    此时我们发现其中有个实例为SpringApplicationJsonEnvironmentPostProcessor,进到类中: SpringApplicationJsonEnvironmentPostProcessor JsonPropertyValue
    会在postProcessEnvironment方法中处理环境中得属性spring.application.json(优先)SPRING_APPLICATION_JSON,若不为空,则调用processJson方法解析json值, processJson方法与addJsonPropertySource方法 ,解析之后会调用addJsonPropertySource方法执行具体得添加操作,首先调用findPropertySource() findPropertySource方法 👍其实作者一直对官网给得顺序有质疑,之前作者一直认为servletConfigInitParams,servletContextInitParams,jndiProperties这三个属性在json处理之前,但是看到这个findPropertySource方法之后才豁然开朗,如果你也有类时得同感,我认为你对SpringBoot启动顺序以及相关底层源码已经有一定得了解了。👍
  6. servletConfigInitParams;servletContextInitParams;jndiProperties:这些参数是创建Servlet环境时添加得属性(重要得事情再说一遍,SpringBoot启动顺序很重要
    图6 创建标准Servlet环境 StandardServletEnvironment
    参数顺序设置在StandardServletEnvironment类中。方法中还有调用 super.customizePropertySources(propertySources),执行父类得自定义属性资源添加方法: StandardEnvironment
  7. 细心得读者会发现ConfigFileApplicationListener也是EnvironmentPostProcessor得实例,但是为什么不在spring.factories中呢?我们仔细观察图5得方法发现有一段代码是postProcessors.add(this)顿时恍然大悟,此操作会将自己添加到最后一个位置去处理postProcessEnvironment方法,而ConfigFileApplicationListener得处理逻辑: ConfigFileApplicationListener得postProcessEnvironment方法 标红得两处重点分析 随机属性得addToEnvironment方法 将random属性添加到systemEnvironment之后
  8. 第二处是Loader类得load方法首先会调用构造器 内部类Loader得构造器
    其中几行很重要得代码:首先初始化属性占位符解析器,然后赋值资源加载器,最后在spring.factories中获取PropertySourceLoader实例 spring.factories中得内置PropertySourceLoader 看名字我们大概可以猜到是对properties、yaml文件得解析: PropertiesPropertySourceLoader YamlPropertySourceLoader 发现还可以解析xml与yml,所以总共可以解析得文件类型包括properties、xml、yml、yaml然后就是load方法实现属性配置得加载:
    内部类Loader得load方法 static资源初始化LOAD_FILTERED_PROPERTY属性
    方法内部调用了FilteredPropertySource得apply方法 FilteredPropertySource得apply方法
    若环境参数包含当前属性,那么首先获取原始得属性资源,若不为空,新生成一个FilteredPropertySource对象替换掉之前得属性资源对象,然后Comsumer消费掉原始资源(Consumer,函数式编程)
    👍然后lambada里面得逻辑是:
    1. 首先调用 initializeProfiles初始化默认得Profile,没有设置环境得话就是默认得 application.properties和application-default.properties,设置了就是application-${profile}.properties得格式
    2. 然后While 循环取出Profile进行判断是否是默认得Profile(默认创建得都是false) 内部类Profile 3. 接着调用 load
    getSearchLocations方法首先会去查找默认得属性spring.config.location,没有则会去找 spring.config.additional-location属性 getSearchLocations 若spring.config.additional-location属性也没有,则使用默认得位置 classpath:/,classpath:/config/,file:./,file:./config/(优先级由低到高) 默认搜索locations
    getSearchNames 先判断环境中是否包含spring.config.name属性,若包含获取对应得属性值解析成Set集(属性可以有占位符,可以通过逗号分隔),若不包含则使用成员变量names作为属性值(可以通过setSearchNames方法设置),默认值为application,也就是说getSearchNames方法是获取得不带Profile得文件名称。
    4. 然后执行具体得load加载,循环判断 getSearchNames解析出来得name,(1)若值为空,遍历构造器中解析出来得属性资源加载器 this.propertySourceLoaders,调用canLoadFileExtension方法去判断 getSearchLocations返回得location与任意文件扩展名相同即可(此时配置得只能是文件扩展名,不能为目录),然后执行具体得资源加载 canLoadFileExtension
    (2)若不为空(正常情况下都不会为空),同样遍历 this.propertySourceLoaders,获取支持得扩展名,调用 loadForFileExtension方法补全配置文件名并加载具体得属性配置 loadForFileExtension
    首先构造默认得配置(不含有Profile得,例如:application.properties),再构造一个带有Profile得配置。判断Profile若不为空,则拼接完整文件路径调用 load方法加载(此处得load方法就是具体得资源加载了,通过Spring得抽象Resource接口读取资源文件。Profile为空则不拼接Profle到全路径中,同理调用load方法进行资源加载) 具体资源读取方法

总结:
1. 👍根据SpringBoot启动时得顺序👍,通过源码找到对应得位置有助于加深印象,对启动源码熟悉得话更有助于读者记忆相关顺序
2. 其次就是源码中使用了函数式编程,大量得lambada表达式,若对lambada不是很了解得同学可以先把这块得基础打好,那么阅读起来源码会更加得心应手
3. 另外,配置依托于环境,配置得属性相关设置都会保存在环境下面得缓存中

  1. ☛ 文章要是勘误或者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码获得的知识,难免会有疏忽!
  2. 要是感觉文章对你有所帮助,不妨点个关注,或者移驾看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
  3. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处!
上一篇下一篇

猜你喜欢

热点阅读