Spring Boot 外部化配置最佳实践指南
🌟 总体理解:什么是“外部化配置”?
Externalized Configuration 的核心思想是:
把应用程序的配置(如数据库地址、端口号、日志级别等)从 Java 代码中剥离出来,放在外部文件或环境中,这样可以在不同环境(开发、测试、生产)使用相同的代码,但加载不同的配置。
Spring Boot 支持多种方式来实现配置的外部化:
.properties
文件.yml
/.yaml
文件- 环境变量(Environment Variables)
- 命令行参数(Command-line arguments)
- JSON 字符串(通过系统属性或环境变量注入)
- JNDI、Servlet 初始化参数等
这些配置最终都会被统一加载到 Spring 的 Environment
对象中,并可以注入到 Bean 中使用。
🔢 配置优先级顺序(PropertySource Order)
Spring Boot 有一套严格的配置加载顺序,后加载的会覆盖先加载的。以下是按优先级从低到高排列的(即:后面的可以覆盖前面的值):
优先级 | 来源 |
---|---|
1 | Devtools 全局设置($HOME/.config/spring-boot ) |
2 | @TestPropertySource 注解(测试专用) |
3 | 测试注解上的 properties 属性(如 @SpringBootTest(properties = {...}) ) |
4 | 命令行参数(如 --server.port=9000 )✅ 最高优先级之一 |
5 | SPRING_APPLICATION_JSON (内联 JSON 字符串) |
6 | ServletConfig 初始化参数 |
7 | ServletContext 初始化参数 |
8 | JNDI 属性 |
9 | JVM 系统属性(System.getProperties() ) |
10 | 操作系统环境变量 ✅ 常用于云环境 |
11 | random.* 随机值生成器 |
12 | 外部 profile-specific 配置文件(如 application-dev.properties ) |
13 | 内部(jar 包内)profile-specific 配置文件 |
14 | 外部 application.properties |
15 | 内部 application.properties |
16 | @PropertySource 注解(注意:加载较晚,不能影响早期配置) |
17 | 默认属性(通过 SpringApplication.setDefaultProperties() 设置) |
📌 关键点:
- 外部配置 > 内部配置
- Profile-specific 配置 > 普通配置
- 命令行参数 > 大多数其他方式(非常强大)
@PropertySource
加载时机较晚,不能用于控制日志、主类等早期配置
💡 实例说明:@Value 注入配置
@Component
public class MyBean {@Value("${name}")private String name;
}
这个 name
的值可以从多个地方来:
- jar 包内默认值:
src/main/resources/application.properties
中定义name=defaultName
- 外部覆盖:在 jar 包同目录下放一个
application.properties
,写name=prodName
→ 会被优先加载 - 命令行指定:运行时加参数
java -jar app.jar --name="Spring"
→ 最终值就是"Spring"
✅ 这就是“一套代码,多环境部署”的基础。
🔍 调试技巧:使用 /env
和 /configprops
端点
Spring Boot Actuator 提供了两个重要端点用于诊断配置问题:
/actuator/env
:查看当前所有生效的配置及其来源/actuator/configprops
:查看@ConfigurationProperties
绑定的对象状态
👉 当你发现某个配置没生效时,可以用这两个接口查清楚它到底从哪来的、有没有被覆盖。
🌐 支持通配符路径(Wildcard Locations)
Spring Boot 支持配置目录使用通配符,例如:
--spring.config.location=config/*/ # 加载所有 config 下的子目录
使用场景:Kubernetes 配置挂载
在 Kubernetes 中,你可能把不同服务的配置分别挂载为 ConfigMap:
/config/redis/application.properties
/config/mysql/application.properties
如果你设置 config/*/
,Spring Boot 会自动扫描并合并这两个文件中的配置。
📌 注意:
- 通配符路径必须以
/
结尾(如config/*/
) - 按文件绝对路径字母排序加载
- 适用于目录,不适用于单个文件
🧩 SPRING_APPLICATION_JSON:用 JSON 注入配置
你可以通过环境变量或系统属性传入 JSON 格式的配置:
方法一:环境变量(Unix/Linux)
$ SPRING_APPLICATION_JSON='{"acme":{"name":"test"}}' java -jar myapp.jar
等价于配置了:
acme.name=test
方法二:JVM 系统属性
$ java -Dspring.application.json='{"name":"test"}' -jar myapp.jar
方法三:命令行参数
$ java -jar myapp.jar --spring.application.json='{"name":"test"}'
方法四:JNDI
java:comp/env/spring.application.json
⚠️ 注意:JSON 中的 null
值不会覆盖低优先级的已有配置(视为“缺失”而非“设为空”)
🎲 RandomValuePropertySource:生成随机值
用于注入随机数,适合测试或生成密钥:
my.secret=${random.value}
my.number=${random.int}
my.uuid=${random.uuid}
my.number.less.than.ten=${random.int(10)} # 0~9
my.number.in.range=${random.int[1024,65536]} # 1024~65535
⌨️ 命令行参数处理
默认情况下,Spring Boot 会把 --xxx=yyy
这样的参数转为配置项加入 Environment
。
例如:
java -jar app.jar --server.port=9000 --debug
等价于设置了:
server.port=9000
debug=true
✅ 命令行参数优先级极高,常用于临时调试或 CI/CD 动态配置。
如果你想禁用这个功能:
SpringApplication app = new SpringApplication(MyApp.class);
app.setAddCommandLineProperties(false); // 禁用命令行参数解析
app.run(args);
📁 application.properties 的加载位置
Spring Boot 会在以下位置查找 application.properties
(按优先级从高到低):
file:./config/
(当前项目根目录下的 config 文件夹)file:./
(当前项目根目录)classpath:/config/
(类路径下的 config 包)classpath:/
(类路径根目录)
👉 越靠近项目的外部配置,优先级越高。
你可以自定义配置文件名:
java -jar myapp.jar --spring.config.name=myproject
# 会去加载 myproject.properties 而不是 application.properties
也可以指定配置文件路径:
java -jar myapp.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties
🧩 spring.config.location vs spring.config.additional-location
参数 | 行为 | 示例 |
---|---|---|
spring.config.location | 替换默认位置 | --spring.config.location=custom/ → 只加载 custom/ 目录 |
spring.config.additional-location | 追加额外位置(先加载) | --spring.config.additional-location=custom/ → 先加载 custom/,再加载默认位置 |
📌 使用 additional-location
可以实现“默认配置 + 局部覆盖”的模式。
🌐 Profile-specific Properties(环境特定配置)
命名规则:application-{profile}.properties
或 application-{profile}.yml
比如:
application-dev.properties
application-prod.yml
加载逻辑:
- 如果激活了
dev
环境,则加载application-dev.properties
- Profile-specific 文件总是覆盖普通文件
- 多个 profile 激活时,后激活的优先级更高(last-wins)
可以通过以下方式激活 profile:
--spring.profiles.active=dev,mysql
或者在 application.properties
中设置:
spring.profiles.active=dev
⚠️ 注意:
- 如果你用了
spring.config.location
指定了具体文件,不会自动加载 profile 变体 - 推荐使用目录形式,如
config/*/
来支持 profile 文件
🔁 占位符(Placeholders)支持
可以在 .properties
文件中引用其他已定义的属性:
app.name=MyApp
app.description=${app.name} is a Spring Boot application
结果:app.description = MyApp is a Spring Boot application
这在简化长配置时很有用。
🔐 加密属性(Encrypting Properties)
Spring Boot 本身不提供加密功能。
但你可以通过实现 EnvironmentPostProcessor
接口,在应用启动前修改 Environment
中的属性值,从而实现解密。
例如:
- 读取加密的数据库密码
- 在
EnvironmentPostProcessor
中调用解密算法 - 替换原始值为明文
📌 推荐方案:
使用 Spring Cloud Vault 或 HashiCorp Vault 来集中管理敏感配置。
📄 使用 YAML 替代 Properties
YAML 是 JSON 的超集,更适合表达层级结构。
示例:
environments:dev:url: https://dev.example.comname: Developer Setupprod:url: https://prod.example.comname: Production
等价于:
environments.dev.url=https://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=https://prod.example.com
environments.prod.name=Production
列表写法:
my:servers:- dev.example.com- prod.example.com
转换为:
my.servers[0]=dev.example.com
my.servers[1]=prod.example.com
要绑定到 Java 对象,需定义 List<String>
类型:
@ConfigurationProperties("my")
public class MyConfig {private List<String> servers = new ArrayList<>();// getter/setter
}
📑 多 Profile 的 YAML 写法(---
分隔)
YAML 支持在一个文件中写多个 profile 的配置:
server:address: 192.168.1.100---
spring:profiles: development
server:address: 127.0.0.1---
spring:profiles: production & eu-central
server:address: 192.168.1.120
- 用
---
分隔不同文档 - 每个文档可以用
spring.profiles
指定适用环境 - 支持表达式:
production & (eu-central | eu-west)
- 支持取反:
!test
表示“非 test 环境”
📌 注意:不要混用 profile-specific 文件(如 application-dev.yml
)和多文档 YAML,否则嵌套文档可能被忽略。
⚠️ YAML 的局限性
- ❌ 不能用
@PropertySource
注解加载 YAML 文件- 所以如果你必须用
@PropertySource
,就只能用.properties
文件
- 所以如果你必须用
- ⚠️ 在 profile-specific 的 YAML 文件中使用
---
多文档语法可能导致意外行为- 因为文件本身已经是 profile-specific,内部的
spring.profiles
可能被忽略
- 因为文件本身已经是 profile-specific,内部的
✅ 建议:要么全用多文档 YAML,要么全用 profile-specific 文件,不要混用
✅ 总结:核心要点一览
主题 | 关键结论 |
---|---|
配置来源 | properties、yaml、环境变量、命令行、JSON、JNDI 等 |
优先级顺序 | 命令行 > 环境变量 > 外部文件 > 内部文件 > 默认值 |
Profile 配置 | application-{profile}.xxx ,优先级高于普通配置 |
YAML vs Properties | YAML 更适合复杂结构,Properties 更通用 |
通配符路径 | config/*/ 可用于 Kubernetes 多 ConfigMap 场景 |
调试工具 | /actuator/env 查看所有配置来源 |
随机值 | ${random.int} , ${random.uuid} |
占位符 | ${app.name} 引用其他属性 |
加密 | 需自行实现 EnvironmentPostProcessor 或用 Vault |
最佳实践 | 外部配置 + profile + YAML + 命令行参数组合使用 |
🛠 实际开发建议
- 本地开发:用
application-dev.properties
+ IDE 运行参数 - 测试环境:CI/CD 中通过
--spring.profiles.active=test
激活 - 生产环境:
- 使用
--spring.config.location=file:/etc/myapp/config/
指向外置目录 - 敏感信息通过
SPRING_APPLICATION_JSON
或 Vault 注入 - 用
--spring.profiles.active=prod
激活生产配置
- 使用
- K8s 部署:
- 用 ConfigMap 挂载多个 YAML 文件到
config/*/
- 使用
spring.config.location=config/*/
自动合并
- 用 ConfigMap 挂载多个 YAML 文件到