几乎每个 Spring Boot 项目都遇到过
排查问题时最让人崩溃的一类故障是——
配置改了重启了,行为还是老的。
经验稍丰富的开发会立刻反应过来:它从别处加载了更高优先级的配置,覆盖了你的修改。但这"别处"具体是哪里?覆盖顺序是什么?为什么有些配置改了没生效,有些反而立刻生效?
Spring Boot 的配置加载体系在文档里写得相对分散,本文把它完整拉直说一遍——从 application.yml 到环境变量、从 bootstrap.yml 到 EnvironmentPostProcessor、从 --server.port=8080 到 K8s ConfigMap,把所有渠道按优先级排成一张图。
一、Spring Boot 配置的"位置"全集
Spring Boot 能从这些地方读取配置(优先级从高到低):
优先级越靠上的源覆盖越靠下的源——同一个 key 多处定义时,最高优先级的值生效。
记住一个原则:
“外部覆盖内部、命令行覆盖文件、Profile 覆盖默认”——这三句话覆盖了 95% 的优先级问题。
二、为什么命令行优先级最高
Spring Boot 把命令行优先级设到最高,是为了给运维一个『不需要改代码也能改配置』的口子:
| |
无论 jar 包里 application.yml 写了什么,命令行说了算。
K8s 里也是这个意思——通过 ConfigMap 传环境变量,环境变量优先级高于 jar 内配置,就实现了"同一个 jar 部署到不同环境"。
三、application.yml 在哪些位置会被找到
Spring Boot 启动时按以下顺序查找配置文件(Spring Boot 2.3+ 默认):
| |
file:开头表示文件系统位置classpath:是 jar 包内- 越靠前的位置优先级越高
file:./config/*/经常被忽视——K8s 把多个 ConfigMap 分别挂到config/redis/、config/db/等子目录时,就靠这一项被加载
这就是为什么"jar 同级目录放 application.yml"能覆盖 jar 内配置——是 #2 覆盖 #4 的体现。
文件名默认是 application,可以通过 spring.config.name 改:
| |
会去找 myapp.yml / myapp.properties。
spring.config.location 能完全自定义查找路径:
| |
四、Profile 机制:环境维度切换
激活 Profile
| |
或者环境变量:
| |
Profile 文件命名
| |
激活某个 profile 时,application.yml 和 application-{profile}.yml 都会被加载,profile 文件覆盖通用文件。
多 Profile 同时激活
| |
按数组顺序加载,后面的覆盖前面的。这是做"基础 prod 配置 + 加监控 + 加调试日志"组合的标准姿势。
spring.profiles.include
不同于 active 是"激活什么",include 是"再附加什么":
| |
激活 prod 时自动也加载 application-monitoring.yml——保证一组配置成套生效,不用每次写命令行参数。
五、bootstrap.yml vs application.yml
Spring Cloud 项目里还会出现 bootstrap.yml——它是 Spring Cloud 引入的,比 application.yml 更早加载。
bootstrap.yml 的典型用途——配置"配置中心客户端本身":
| |
为什么必须更早加载?因为 Nacos / Apollo / Spring Cloud Config 的客户端要先连上配置中心,才能拉到 application.yml 里的业务配置——鸡生蛋、蛋生鸡的问题。
Spring Cloud 2020.0 之后官方推荐用
spring.config.import替代bootstrap.yml:
1 2 3spring: config: import: optional:nacos:application.yml但
bootstrap.yml仍然兼容,且大量老项目还在用。
六、外部化配置:12-Factor 的最佳实践
12-Factor App 第三条:“Store config in the environment”——配置应当从环境而来,而不是写死在代码里。Spring Boot 的设计完全贴合这个理念:
| |
${VAR:default} 语法——先看环境变量,没有就用默认值。这种写法的好处:
- 同一个 jar 在 dev/test/prod 三套环境跑
- K8s 里通过 ConfigMap / Secret 注入
- 本地开发不需要 Secret,用默认值
K8s 部署时:
| |
七、@Value vs @ConfigurationProperties
业务代码注入配置有两种主流方式。
@Value:单字段注入
| |
简单直接,但有几个限制:
- 不支持类型校验——拼写错了运行时才发现
- 不支持嵌套对象——只能拿单个值
- 不便于测试——要在 test 里 mock 整个 Environment
@ConfigurationProperties:批量映射
| |
| |
@ConfigurationProperties 是生产推荐姿势——类型安全、能 @Validated 校验、IDE 提示完整、便于单元测试。
@RefreshScope
配置中心场景下,要让配置变更立刻生效需要加 @RefreshScope:
| |
但记住:
@RefreshScope不要加在@Configuration上@ConfigurationProperties自带的刷新已经够多数场景用,不一定需要@RefreshScope- 复杂业务的"动态配置"建议直接通过事件订阅,而不是依赖
@RefreshScope重建 Bean
八、调试技巧:搞清楚配置从哪里来
1. Actuator 的 /actuator/env
| |
调用 /actuator/env 能看到每个 PropertySource、每个 key 的来源和最终值——是排查"配置不生效"的最快办法。
| |
2. 启动日志 DEBUG
| |
会打出每个 starter 的自动配置生效情况。
3. 启动参数检查
如果你怀疑有命令行参数在覆盖配置,看启动脚本里的所有 -D 和 --,再看环境变量:
| |
很多"诡异覆盖"是 K8s ConfigMap 里悄悄塞了同名变量。
九、几个最常被踩的坑
坑 1:YAML 缩进与冒号后空格
| |
| |
坑 2:Profile 里的相对路径
| |
部署到容器后工作目录可能不是你以为的位置——生产路径写绝对路径。
坑 3:环境变量大小写转换
YAML 里的 spring.datasource.url 对应环境变量是 SPRING_DATASOURCE_URL——点变下划线、全大写。写成 spring.datasource.url 当环境变量是不会被认到的(虽然 Spring Boot 现在支持驼峰转 kebab,但环境变量必须全大写)。
坑 4:占位符递归
| |
启动直接报错。但更隐蔽的是:
| |
会展开成预期之外的字符串,自己看不出来。复杂占位符建议拉直写。
坑 5:@ConfigurationProperties 没生效
最常见原因是没加 @Component 或 @EnableConfigurationProperties:
| |
或者:
| |
二者选其一。
十、生产 Checklist
部署前过一遍:
- 不在代码 / yml 里硬编码密钥、数据库密码
- 敏感配置通过环境变量 / Secret 注入
- 不同环境分别有
application-{env}.yml -
--spring.profiles.active在启动脚本明确传入 - 业务代码用
@ConfigurationProperties+@Validated - 路径相关配置写绝对路径
- Actuator
env和configprops在生产环境关闭或加权限 - 大型项目的全局配置走
bootstrap.yml或spring.config.import - 启动后用
/actuator/env抽查关键配置的来源
小结
把全文压一句:
Spring Boot 配置加载的本质是『多个 PropertySource 按优先级合并』——只要把『谁覆盖谁』这件事弄明白,所有"配置不生效"的问题都能 5 分钟定位。
记住几条核心规则:
- 优先级:命令行 > 环境变量 > 外部文件 > 内部文件 > 默认值
- Profile:通用 + profile 配置都加载,profile 覆盖通用
- 业务用
@ConfigurationProperties,少用@Value - 配置中心走
bootstrap.yml/spring.config.import - 排查用
/actuator/env
把这些吃透,配置层基本不会再出意外。