Spring Boot 属性配置你所不知道的细节
今天我们要聊的这个问题,可能工作5年的资深程序员也不一定搞得很清楚,但是我敢保证在开发 Web 应用过程中大家都遇到过。
这个问题就是: Spring Boot 应用程序读取配置属性时,不同配置源的优先级是个啥情况?
那如果你对这个问题没啥概念,我给大家说几个场景,你肯定就明白了?
应用程序的数据库配置,我们一般都是通过配置文件配置的?不会有人直接硬编码写的吧;
应用开发中往往都需要加一些开关来控制业务逻辑,比如新功能灰度之类的场景;
开发、测试、生产环境需要不同的值,这种变量一般也都是通过外置配置来读取的,不会直接写死的;
我想上面这几种场景,搞过 Java 开发的总归会遇到的,今天我们要聊的就是这些外置的配置属性,配置在不同的地方,如果属性名一样的情况下,到底谁的优先级高?也可以说配置在高优先级地方的属性值会覆盖低优先级位置的同名属性值。
说实话,这个问题我身边很多工作了多年的资深开发都没有搞明白,不过这个并不影响他们开发,大不了调试一波嘛,就是多费点时间。但是也有特别骚的情况,他们也头大。
比如生产环境莫名其妙的出了问题,排查是因为一个配置属性值不对导致的,但是测试环境看了没问题,然后项目中的配置文件配置的也没问题,这种就很烦人了。
排查到最后一般就会发现:生产环境的启动命令加了命令行参数或者 JVM 系统属性,比这还骚的就是配到了系统的环境变量里面。。
那今天这个问题搞懂了,就有以下几个好处:
可以帮助你高效的开发出符合预期的应用程序;
在排查一些老代码导致的异常时,多一个排查思路,看看是否是不同环境的属性配置不同(经常有一些老项目的属性值是配置在环境变量里,覆盖了代码里面的属性配置);
提高开发效率(在不改项目配置的情况下,通过高优先级的属性源覆盖对应的属性)
Spring Boot 常用的属性源
下面列举出我们平时常用的一些配置属性源:
这些属性源其实我已经按照优先级从高到低的顺序排列了,也就是说命令行参数的优先级最高,其次是 JVM 系统属性,默认属性优先级最低,同名属性将会遵循高优先级覆盖掉低优先级。
这其实就是本文的最终结论了,后面就是介绍各种属性源如何设置,进行实操验证了。你如果不想往后看了,直接收藏这张图片也是可以的哦~
这些属性配置的方法部分小伙伴可能没有用过,接下来我们用一个例子来实战演示下,也推荐你动手实践,试试结论是否正确,这样理解记忆会更加深刻。
验证配置属性的优先级
为了能够清晰高效的达到我们验证各个属性源配置属性优先级的目的,这里的思路就是:先在低优先级的属性源配置,然后配置高优先级,如果覆盖了就说明我们的结论没毛病。
首先我们使用 Spring Boot 快速写一个简单的 Web 接口,然后直接返回配置的属性名:
@RestController
public class ReadingListController {
// 获取属性配置的类
@Resource
private MyProperties myProperties;
@GetMapping("/getMaxValue")
public String getProperties () {
return "配置的属性值 = " + myProperties.getMaxValue();
}
}
上面我们使用了一个专门用来获取配置属性的 Bean,不建议直接在 Controller 引用属性,这个大家平时在开发中也要注意下:
@Component
@ConfigurationProperties(prefix= "my" )
@Data
public class MyProperties {
// 默认属性值
private int maxValue= 0;
}
那上面的代码就是我们测试所需要的全部代码了,接下来我们从最低优先级的属性源开始配置。
默认属性
这个不用多说,就是直接在 Java 代码中声明变量时赋予的值;
启动应用程序后,访问我们的测试接口:http://localhost:8000/getMaxValue
通过 @PropertySource 标注的属性源
这种属性配置的方法我们一般也经常用,就是自定义一个配置文件,然后用 @PropertySource 注解来指定加载。
上面的代码,就会加载我们定义在 classpath 路径下的 my.properties
文件了。我们在自定义的属性配置文件 my.properties
中添加配置属性:
my.maxValue=1
然后重启应用,访问我们的测试接口,得到的输出如下:
这就证明了 @PropertySource
属性源配置覆盖了默认配置。
application.properties配置文件
Spring Boot 应用程序,默认会加载 classpath 路径下 application.properties
和 application.yml
文件中配置的属性。这也是我们开发时最常用的属性配置源了。
在重启应用,访问我们的测试接口,就会得到结果值 2 了。说明应用内的 application.properties
文件中配置的属性优先级高于 @PropertySource
指定的自定义配置文件。
这里需要注意,如果 classpath 路径下同时存在 application.yml 和 application.properties 文件,那么 application.yml 文件中的属性优先级高于 application.properties,这一点大家也可以自己测试下。
操作系统环境变量
这是一种骚操作了,在不同的环境设置了不同的操作系统环境变量,任你看代码找破天也是一头雾水。所以下次出现不同环境配置属性值不符合预期的场景,操作系统环境变量记着检查下。
这里我使用的 zsh,可以使用 export 来设置临时的操作系统环境变量,当然 bash 也可以这样配置。
export my_maxvalue=3
细心的同学发现了,变量的命令我使用了下划线而不是点,这是因为操作系统对变量名称的要求。
然后我们将应用打包,我这里使用的 gradle 来构建项目,只需要执行:gradle build
, Spring Boot 提供的 gradle 插件就会帮我们将所有需要的依赖打包到一起,得到一个可执行的 jar 包。
然后我们启动应用程序:java -jar build/libs/spring-road02-0.0.1-SNAPSHOT.jar
访问测试接口得到的值就是3了。
结论证明:操作系统环境变量配置的属性优先级又高于上面我们所列举的。
JVM系统属性
这也是生产环境使用最多的一种差异化属性设置方式,在应用程序的启动脚本命令中指定属性值,好处就是其他人不能随意修改,一般只能运维人员来编写的。常见于配置一些影响应用程序运行稳定性的参数,比如内存分配、GC参数等的配置。
还是使用上面我们打好的 jar 包,在运行时增加 JVM 系统属性即可:
java -Dmy.maxvalue=4 -jar build/libs/spring-road02-0.0.1-SNAPSHOT.jar
然后访问测试接口得到的值就变为了4。
命令行属性配置
这个和 JVM 系统属性配置的使用场景差不多,只不过设置方式有点区别,直接看下命令行启动配置就明白了:
java -Dmy.maxvalue=4 -jar build/libs/spring-road02-0.0.1-SNAPSHOT.jar --my.maxvalue=5
启动后,我们继续访问测试接口看看结果:
结果证明,命令参数的优先级比 JVM 系统属性优先级高,会将其覆盖。
总结
通过今天的实践学习,大家应该对 Spring Boot 属性配置常见的几种来源有了更加清晰的认识,并且通过实验测试了它们具体的优先级。我们再来汇总整理下:
按照优先级的高低从上往下排列,也就是说命令行参数配置的属性优先级最高,会覆盖其它属性源的配置,以此类推,记不住的小伙伴可以收藏下,以备不时之需。
今天就先聊到这里,更多关于 Spring 相关的内容,也在持续更新中,敬请关注!
有道无术,术可成;有术无道,止于术
欢迎大家关注Java之道公众号
好文章,我在看❤️