Spring Boot 配置文件加载顺序踩坑记
前言
上个月上线了一个新功能,本地测试一切正常,发到生产环境后,数据库连接突然报错。排查了整整一下午,最后发现是 配置文件加载顺序 导致的坑。
Spring Boot 的配置文件加载顺序看似简单,实际上有很多细节容易忽略。
问题现象
项目结构:
1 | src/main/resources/ |
application-prod.yml 中配置了生产环境数据库:
1 | spring: |
打包时指定了 prod 环境:
1 | mvn clean package -Dspring.profiles.active=prod |
但启动后日志显示连接的是 开发环境数据库!
排查过程
第一步:确认激活的 Profile
1 | java -jar app.jar --debug |
查看启动日志:
1 | Active profiles: prod |
Profile 确实激活了,但配置没生效?
第二步:查看配置加载顺序
加上 --debug 参数,Spring Boot 会输出详细的配置加载日志:
1 | java -jar app.jar --debug 2>&1 | grep "Loaded config file" |
输出:
1 | Loaded config file 'file:./application.yml' |
等等,file:./application.yml 是什么?
第三步:发现罪魁祸首
原来运维同事在启动脚本所在的目录放了一个 application.yml:
1 | ls -la /opt/myapp/ |
这个外部配置文件 覆盖了 jar 包内的 application-prod.yml 配置!
Spring Boot 配置文件加载顺序
Spring Boot 的配置文件按以下优先级加载(后加载的覆盖先加载的):
1. 默认位置(优先级从低到高)
| 优先级 | 位置 | 说明 |
|---|---|---|
| 1 | classpath:/ |
jar 包内的根目录 |
| 2 | classpath:/config/ |
jar 包内的 config 目录 |
| 3 | file:./ |
当前运行目录 |
| 4 | file:./config/ |
当前运行目录的 config 子目录 |
| 5 | file:./config/*/ |
当前运行目录 config 的任意子目录 |
2. 配置文件类型优先级
同一位置下:
1 | application.properties < application.yml < application.yaml |
3. Profile 文件加载顺序
以 application-prod.yml 为例:
1 | application.yml -> application-prod.yml |
Profile 配置文件会 覆盖 主配置文件中相同的属性。
4. 完整的优先级(从高到低)
- 命令行参数:
--server.port=8081 SPRING_APPLICATION_JSON环境变量ServletConfig初始化参数ServletContext初始化参数- JNDI 属性
- Java 系统属性:
System.getProperties() - 操作系统环境变量
RandomValuePropertySource:random.*- jar 包外部的 Profile 配置文件:
file:./application-prod.yml - jar 包内部的 Profile 配置文件:
classpath:/application-prod.yml - jar 包外部的配置文件:
file:./application.yml - jar 包内部的配置文件:
classpath:/application.yml @PropertySource注解加载的配置- 默认属性:
SpringApplication.setDefaultProperties()
关键结论:jar 包外部的
application.yml优先级高于 jar 包内部的application-prod.yml!
坑一:外部配置覆盖 Profile 配置
这是我最开始遇到的问题。运维在启动目录放了开发环境的 application.yml,导致生产环境配置被覆盖。
解决方案
方案 1:使用 spring.config.location 限制配置位置
1 | java -jar app.jar --spring.config.location=classpath:/ |
只从 classpath 加载配置,忽略外部文件。
方案 2:使用 spring.config.name 区分配置名
1 | java -jar app.jar --spring.config.name=application-prod |
直接指定加载 application-prod.yml,不加载默认的 application.yml。
方案 3:运维规范 - 统一使用外部 config 目录
1 | # 启动时指定配置文件位置 |
将配置文件统一放在 config 目录,避免和 jar 包混在一起。
坑二:bootstrap.yml 加载时机
使用 Spring Cloud Config 时,bootstrap.yml 在 application.yml 之前加载,用于配置中心连接信息。
但如果不小心在 bootstrap.yml 中放了业务配置:
1 | # bootstrap.yml |
这个配置会被 application.yml 覆盖吗?
答案:不会! bootstrap.yml 和 application.yml 是不同的上下文,bootstrap.yml 中的配置不会被 application.yml 覆盖。
正确用法
bootstrap.yml 只放配置中心相关配置:
1 | spring: |
业务配置全部放在 application.yml 或 Profile 配置文件中。
坑三:@Value 注入时机
1 |
|
如果在 @PostConstruct 中使用 @Value 注入的值,可能会拿到旧值或 null。
解决方案
使用 @EventListener(ApplicationReadyEvent.class):
1 |
|
或者实现 EnvironmentAware:
1 |
|
坑四:配置加密与解密顺序
使用 Jasypt 加密配置时:
1 | spring: |
如果加密器初始化晚于配置加载,会导致解密失败。
解决方案
确保 Jasypt 配置在 bootstrap.yml 中:
1 | jasypt: |
或者使用 Spring Boot 的 EnvironmentPostProcessor:
1 | public class DecryptEnvironmentPostProcessor implements EnvironmentPostProcessor { |
在 META-INF/spring.factories 中注册:
1 | org.springframework.boot.env.EnvironmentPostProcessor=\ |
最佳实践
1. 项目配置规范
1 | src/main/resources/ |
application.yml 中只放不随环境变化的配置:
1 | spring: |
2. 启动脚本规范
1 |
|
3. 配置验证
启动时打印实际生效的配置:
1 |
|
4. 使用 Actuator 查看配置
添加依赖:
1 | <dependency> |
配置端点:
1 | management: |
访问 http://localhost:8080/actuator/env 查看所有配置来源和值。
总结
| 坑 | 原因 | 解决方案 |
|---|---|---|
| 外部配置覆盖 Profile | file:./application.yml 优先级高于 classpath:/application-prod.yml |
使用 spring.config.location 限制加载位置 |
| bootstrap.yml 配置不生效 | 加载时机不同,属于不同上下文 | 只在 bootstrap 中放配置中心相关配置 |
| @Value 注入时机不对 | @PostConstruct 执行时配置可能未完全加载 |
使用 ApplicationReadyEvent 或 EnvironmentAware |
| 加密配置解密失败 | 加密器初始化晚于配置加载 | 使用 EnvironmentPostProcessor |
Spring Boot 的配置加载机制很灵活,但也因此容易踩坑。理解加载顺序,规范配置管理,能避免很多线上问题。
