spring boot最核心的特性就是自动化配置,我们在学习spring boot的时候,首要需要了解它的自动化配置原理,其次是属性文件的加载顺序,我认为这两点是学习spring boot的重中之中。
网上介绍spring boot属性文件的加载顺序的文章很多,但都没有从源码上深入分析。
今天和大家一起通过源码探究,spring boot属性文件的加载顺序。
这里先说明一点:
先加载的属性未必会生效,后加载的属性未必一定会覆盖先加载的属性值。
说明:
加载指的是将属性值读取加载到spring容器中的过程;
生效指的是运用加载到的属性值去初始化bean的过程。
属性值加载的顺序,和属性值生效的顺序没有必然的关系。
为什么要分析?
主要是在实际项目开发过程中,由于属性文件非常多,常常导致属性文件配置混乱,
最常遇到的问题就是:
为什么明明配置了属性,但项目启动就是怎么都不生效的问题?
所以,搞清楚属性文件的读取和生效的顺序,对日常基于spring boot的开发过程中非常重要。
源码分析
1、通过main启动类,找到doInvokeListener()方法建立断点,观察ApplicationEvent中属性源的变化情况:
说明:测试的工程注册在eurake中,并从配置中心config中加载属性。
2、最先加载的3个属性源
Inlined Test Properties 单元测试属性
systemProperties JVM系统属性
SystemEnvironmentProperty 系统环境变量属性3、初始化ConfigFileApplicationListener监听器时,新加载了如下数据源
configurationProperties
配置属性源,这个属性源比较特殊,他不会去自己加载配置属性,而是将environment中已经加载到的属性源按顺序放入存放进来,并将自己放到propertySourceList的头部first。(不是很好理解,个人感觉可能是一种缓存机制)
random 随机数属性源
springCloudClientHostInfo spring cloud客户端host相关属性源
bootstrap-dev.properties
bootstrap.properties
defaultProperties4、加载配置中心属性
可以发现,配置中心属性源configService虽然是后面加载的,但是被放在了propertySourceList的头部。5、启动完成后,environment中propertySourceList的顺序
feign feign调用的属性源
systemProperties java se运行时系统属性
SystemEnvironmentProperty 系统环境变量属性
configService 配置中心属性源
configurationProperties 配置属性源
Inlined Test Properties 单元测试属性
random 随机数属性源
application-dev.propeties application-profile属性源
application.propeties application属性源
springCloudClientHostInfo spring cloud客户端host相关属性源
bootstrap-dev.propeties bootstrap-profile属性源
bootstrap.propeties bootstrap属性源
eureka/server.properties eureka服务属性源
defaultProperties 默认属性源6、读取属性文件的源码分析
springboot 启动时,会初始化各种属性源PropertySource,并把加载的属性源存放到 enviroment 的propertySourceList中。我们在获取属性时,通过遍历propertySourceList的属性源去读取属性值,获取对应属性值就直接返回(先读取先生效),所以在propertySourceList前面的属性源会优先生效。
注意propertySourceList的类型是 CopyOnWriteArrayList ,即线程安全的ArrayList
7、开启属性获取日志,监控属性获取过程
logging.level.org.springframework.core.env=trace
通过注入Environment,查看执行属性加载顺序
@SpringBootTest
@Slf4j
class DemoApplicationTests {
@Autowired
Environment environment;
@Test
void getProperties() {
System.out.println(environment.getProperty("spring.user.name"));
}
}
properties和yml配置文件加载顺序
一般在spring boot中最好统一只使用一种类型的配置文件,避免出现配置混乱。
yml配置文件具有更好的格式,更强大的功能,但是对属性文件的配置格式要求比较。格式出错导致属性读取不到的问题比较难排查,且利用属性的键值搜索指定属性时,也不是很方便。properties属性文件,好处就是简单,不容易出现格式方面的问题,便于属性查找。
假设一个项目在同一位置同时存在application.properties和application.yml文件,
且其中都含有相同的某个key,但value不同,如:
application.properties中:server.port=8001,
application.yml中:server.port=8888。
问题:springboot是否都加载这两个配置文件?如果两个文件有相同的key,取哪一个文件的value?
答: 都加载,且按properties→yml的顺序加载。
在看到spring.factories中,配置加载器顺序是先执行PropertiesPropertySourceLoader再到YamlPropertySourceLoader。在ConfigFileApplicationListener获取server.port这个key的value时候,可以发现两配置文件全都加载进去了,且注意顺序,application.properties文件在前。
getSource()方法获取到两个Source,先从application.properties文件中查找值,一旦找到立即返回,如果找不到再从application.yml中查找。
总结
1、spring boot中先读取的属性不一定先生效,生效的顺序是根据bean初始化时,environment中属性源propertySourceList的顺序来决定的。
2、属性源初始化读取的顺序和最后存放到environment中的propertySourceList的顺序没有必然关系。
environment.getPropertySources().addFirst(source);
environment.getPropertySources().addAfter(source1,source2);
environment.getPropertySources().addBefore(source1,source2);
environment.getPropertySources().addLast(source);
3、属性文件的读取顺序大概是:
java se运行时系统属性 ——》系统环境变量属性——》
bootstrap.properties——》bootstrap-dev.properties ——》configService 配置中心属性源——》application属性源——》 application-dev属性源
注意:
从属性源的加载顺序就可以看出,为什么我们在微服务里面,在业务服务模块进行配置中心配置的时候,一定要在bootstrap属性文件中配置了。
4、项目中yml和properties这两个格式的配置文件,一般只选用一种类型的配置文件。
如果同时配置两种,会优先加载properties的配置文件,且properties的配置属性会优先生效。
5、属性文件的生效的顺序大致是:
java se运行时系统属性 ——》 系统环境变量属性——》
configService 配置中心属性——》application-dev属性源——》 application属性源——》bootstrap-dev.properties ——》bootstrap.properties
我们在java -jar启动脚本中配置的属性,属于 java se运行时系统属性,优先级最高。
6、如果项目中发现配置的属性一直没有生效,可以参考属性源的生效顺序,看看是否出现属性覆盖的问题。
更直接的方式是通过单元测试,执行environment.getProperty(“spring.user.name”),查看属性源列表propertySourceList的顺序,并通过打印日志来判断属性值是从具体哪个属性源加载到的。
希望看完本篇文章,当再被问到spring boot属性文件的加载顺序的时候,你可以胸有成竹的款款而谈。
更多精彩,关注我吧。