type
status
date
slug
summary
tags
category
icon
password
AI summary
背景
我们的配置中心一直是自研的,并且也用了很久,一直也没出现过什么问题。直到最近这段时间,出了两个同类型的问题,于是乎准备来研究并解决一下。
问题原因都是因为引入了某个新包,并且这个新包的classpath根路径下包含了1个application.properties文件,这个时候Spring会加载这个application.properties配置文件。如果该配置文件里有配置中心里没有配置的key-value,那么该配置就会生效(如果配置中心有,那么还是配置中心的优先级更高)。
问题分析
问题的原因很简单。我们知道如果SpringBoot不接入外部配置中心,那么默认就是加载classpath下的application.properties文件。而我们自研的配置中心的client包的实现其实和默认的
ConfigFileApplicationListener
一样,都是利用了EnvironmentPostProcessor
这个扩展点,不过我们的Bean加载的优先级会比默认的更高一些。所以产生上述问题的原因就是:
- 首先我们自定义的实现类优先加载了配置中心的配置文件
- 我们的项目的classpath根路径下没有放置application.properties
- SpringBoot默认的
ConfigFileApplicationListener
加载了classpath下的application.properties,这个时候就可能会加载到其他jar包里的位于classpath下的的application.properties。当然由于jar包加载顺序的不确定性,也没有办法确定具体是加载到哪个application.properties文件
大概原因已经了解了。下面我们再来看看源代码,看看如何解决这个问题。比如看看
ConfigFileApplicationListener
能不能禁用掉?SpringBoot配置文件的加载流程——ConfigFileApplicationListener源码分析
核心都在这个
Loader.load()
方法里,这个类比较长,所以相关的解释我就以注释的形式直接加在对应代码附近,这样阅读起来体验会比较好一些。这里对load方法做一个简单的描述,整个load方法其实只有一行代码:FilteredPropertySource.apply
,这个方法接受4个参数:- enviroment
- propertySourceName(固定为"default.properties")
- filteredProperties(固定为Set["spring.profiles.active", "spring.profiles.include"]),
- Consumer回调
这个apply方法的大致逻辑为:
- 首先会从enviroment里去找名为default.properties的PropertySource
- 如果没找到,会直接调用回调函数,且入参为null。
- 如果找到了,也会调用回调函数,但是入参为找到的PropertySource。但是回调前后会替换enviroment里对应的PropertySource,调用前会过滤掉Set,调用后又会复原(TODO 这一步暂时没有理解具体是什么用意,有了解的朋友可以指教一下)
这里看得不是太明白也没关系,核心还是在Loader类里,结合上面的Consumer回调函数一起看下面的这些代码:
上面这段的逻辑确实有些复杂,主要是因为支持了在文件里配置spring.profiles这种骚操作,不然的话代码逻辑会简单清晰很多。我们再把上面的流程提炼一下:
- 构建profiles列表,来源有几个:
- 固定的一个null元素,是为了加载不带profile的配置文件,如application.properties
- spring.profiles.active
- spring.profiles.include
- Spring容器里通过其他方式配置的Profile,比如通过SpringApplicationBuilder来配置profiles
- 根据1中构建的profiles里的每一个profile,都会应用以下的加载逻辑
- 去searchLocations为目录去加载配置文件,可以通过spring.config.additional-location或者spring.config.location来指定
- 可以通过spring.config.name来指定基础文件名
- 会根据propertySourceLoaders结合基础文件名来加载不同扩展名的配置文件(如xml、properties)
- 每一个组合都包含了4次加载动作
- 加载文件名带profile但是配置文件里没有配置spring.profiles的配置文件
- 加载文件名带profile,配置文件里有配置spring.profiles并且配置的spring.profiles里包含了本profile的配置文件
- 处理之前已经加载过的profile对应的配置文件(加载过profile不代表对应的配置文件被加载进来了,因为还有过滤规则),这个时候如果配置文件里配置的spring.profiles包含了本次的profile,那也会加载该配置文件
- 加载文件名不带profile,这里分两种情况
- profile为null时,此时配置文件里没有配置spring.profiles才会加载
- profile不为null时,此时配置文件里必须配置了spring.profiles并且spring.profiles包含profile时才会加载 上述4种情况都还有一个共同的前提是该profile被active了
- 再一次去searchLocations下加载配置文件,和第2步差不多,但是此时的profile恒为null
- 去searchLocations为目录去加载配置文件,可以通过spring.config.additional-location或者spring.config.location来指定
- 可以通过spring.config.name来指定基础文件名
- 会根据propertySourceLoaders结合基础文件名来加载不同扩展名的配置文件(如xml、properties)
- 由于profile恒为null,所以只会加载文件名不带profile,但是配置文件里配置了spring.profiles,且配置的spring.profiles不为空的配置文件
- 再一次性地把整个加载到的PropertySources添加到环境变量里
- 根据processedProfiles(过滤掉profile为null的情况),重新设置一下activeProfiles
可以看到虽然我已经尽量用精炼的语言总结了,但是整个流程还是相对比较复杂,并且逻辑也不是非常清晰,包括对这部分代码的设计和想法感觉还是不能完全理解到位。希望对这块有更深入理解的同学可以在评论区提点一二。
另外,关于spring.profiles这种配置的用法我着实没有见过,于是也去请教了一下chatgpt

自研配置中心client包的代码分析
下面这一段是我们自研配置中心client包加载完远程配置文件之后注入到环境变量的源代码
可以看到这里的实现也有一些问题,因为这里把配置中心的配置放在了第一位(First)。这样一来,配置中心的配置就是第一优先级了,甚至超过了CommandLine参数。所以这里也需要调整一下,也算是这次review代码的时候看出来的一个问题。这里参考
ConfigFileApplicationListener
的实现调整一下即可:
优化方案
其实上面讲了半天,主要是为了全面的了解SpringBoot加载配置文件的整个流程。如果你对前文的内容已经消化得差不多了,比较容易能想到一些优化方案:
- 修改spring.config.name的配置值,替换成一个不可能用到的名字,如never_exist_999_xxx
- 修改spring.config.location的配置值,替换成一个不可能用到的目录,如never_exist_999_xxx
并且这个还不能写死,因为我们既有使用配置中心的项目,也有使用SpringBoot原生配置文件的项目。所以我们把这一段逻辑放到配置中心client包里:

附录
ClassLoader.getResource
这一段是javadoc告诉我们,通过ClassLoader.getResource
- 如果指定name的资源在多个jar包中存在,只能返回其中一个
- 具体返回哪一个是根据jar包加载的顺序决定的,但是这个顺序是不可预测的
而
ConfigFileApplicationListener
加载配置文件的时候底层调用的就是这个方法
defaultProperties
找了半天我说defaultProperties在哪呢
org.springframework.boot.SpringApplication#configurePropertySources

- Author:黑微狗
- URL:https://blog.hwgzhu.com/article/spring-boot-config-load-process
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts