Spring Boot 的灵活配置能力依赖一套明确的外部配置加载顺序和属性覆盖规则。开发过程中,同一属性可能出现在多个位置——命令行参数、环境变量、application.properties、甚至操作系统环境变量。如果搞不清它们的优先级,就很容易出现“明明改了配置,为什么没生效?”的困惑。
本节将用最直白的方式梳理这些规则,并给出验证方法,帮助你在实际工作中快速定位配置问题。
7.5.1 外部配置的来源
Spring Boot 可以从以下渠道获取配置(按常用程度大致排列):
- 命令行参数(
--server.port=8080) java:comp/env的 JNDI 属性- JVM 系统属性(
-Dserver.port=8080) - 操作系统环境变量
application.properties/application-{profile}.properties文件(内部或外部)application.yml/application-{profile}.yml文件- 其他(如
@PropertySource注解导入的配置、配置中心等)
7.5.2 优先级顺序:从高到低
Spring Boot 为各种来源定义了一套严格的优先级。数字越小,优先级越高,高优先级会覆盖低优先级中的同名属性。
以下列出核心优先级顺序(参考 Spring Boot 2.x/3.x 官方文档,只保留最通用的来源):
- 命令行参数
如 --server.port=9090。这是最高优先级,常用于运维时临时调整端口等关键参数。
- JVM 系统属性
如通过 -Dserver.port=9090 启动应用。
- 操作系统环境变量
如 export SERVER_PORT=9090(环境变量名称通常与属性名对应,但格式需调整:点号替换为下划线,破折号也替换为下划线,并转为大写)。
比如 spring.datasource.url 对应的环境变量是 SPRING_DATASOURCE_URL。
- 随机值属性
当使用 random.* 占位符时生成。
- jar 包外部的
application.properties或application.yml文件
又细分为:
- jar 包同级
config/目录下的配置文件 - jar 包同级目录下的配置文件
- 类路径
/config目录下的配置文件
其中外部文件优先级整体高于内部打包的配置文件。
- jar 包内部的
application.properties或application.yml
@PropertySource显式导入的配置文件
多用于模块化场景。
- SpringApplication.setDefaultProperties 设置的默认属性
在启动类的 main 方法中通过代码指定的兜底值。
实际优先级顺序如下图所示(高优先在上):
命令行 args
JVM 系统属性 (-D)
环境变量
jar 外config/application.properties
jar 外application.properties
classpath 中config/application.properties
classpathapplication.properties
……
默认属性
7.5.3 配置文件自身的查找与覆盖
一个常见问题:如果项目中同时存在多个 application-{profile}.properties 和主 application.properties,它们之间如何工作?
- 主配置文件(
application.properties)始终被加载,作为默认配置。 - 当激活特定的 profile 时(如
spring.profiles.active=dev),与该 profile 对应的配置文件(application-dev.properties)也会被加载。 - profile 专属配置会覆盖主配置中的同名属性,而未指定的属性继续沿用主配置的值。
举例:
# application.properties
server.port=8080
app.name=MyApp
# application-prod.properties
server.port=9090
当以 prod profile 启动时,最终的 server.port 为 9090,而 app.name 依然是 MyApp。这种设计允许:用主文件定义绝大部分默认值,用 profile 文件只覆盖环境相关的不同项。
7.5.4 多位置视图:目录优先级
如果配置文件的分布如下(这是 Spring Boot 会扫描的位置):
运行目录/
├── config/
│ └── application.properties ← 优先级最高
├── application.properties ← 次之
└── yourapp.jar
├── BOOT-INF/classes/config/
│ └── application.properties ← 再次
└── BOOT-INF/classes/
└── application.properties ← 优先级最低
规则:
- jar 外 > jar 内
- config/ 子目录 > 同级目录(即 config/application.properties 优先级高于根目录的 application.properties)
因此运维人员在生产环境部署时,可以在 jar 包同级创建 config/ 目录并放入覆盖配置,而无需重新打包,这是 Spring Boot 提供的一种极其实用的运维模式。
7.5.5 动态属性合并 vs 相同 key 整体覆盖
Spring Boot 遵循的覆盖规则是:同一个属性键(key),后者完全取代前者,而不是部分合并。
- 对于简单属性(字符串、数字、布尔值等),直接取值覆盖。
- 对于集合或 Map 类型的复杂属性,后续配置会整体替换,而不是追加元素。
例如,主配置中定义了:
app.allowed-origins=a,b
在 profile 中定义:
app.allowed-origins=x,y,z
那么最终生效的值为 x,y,z,而不是 a,b,x,y,z。这点需要注意,防止在 profile 中“忘了补全”造成原值丢失。
7.5.6 验证加载顺序与生效值
你可以通过以下方式在开发时直观验证:
1. 打印生效的环境对象
在任意 Bean 中注入环境,并输出属性值:
@Autowired
private Environment env;
@PostConstruct
public void printConfig() {
System.out.println(env.getProperty("server.port"));
}
2. 启动时观察日志
Spring Boot 在启动时会在控制台打印类似 The following profiles are active: dev 的语句,并在特定调试级别下输出已加载的配置文件位置。你也可以设置 logging.level.org.springframework=DEBUG 来获取更多加载细节。
3. 使用 Actuator 的 /env 端点
如果启用了 Actuator,可以访问 /actuator/env 查看所有属性来源及最终值,还会显示每个属性的贡献源(propertySource),这是排查“配置冲突”的最快方式。
7.5.7 实战建议与避坑指南
总结常见问题与最佳实践:
- 生产环境临时调整端口:使用
--server.port=8090命令行参数,而不是去改配置文件。 - 容器化部署:环境变量是注入配置的首选方式,例如
SPRING_DATASOURCE_URL覆盖数据库连接地址。 - 不要滥用 profile 文件:如果 profile 过多,维护成本急剧上升。通常保留
dev、prod,更多差异化通过集中配置中心管理。 - 理解配置文件路径规则:容器部署时,把外部配置挂载到 jar 同级
config/目录,可实现免重新构建的配置修改。 - 注意列表/数组的整体覆盖特性:在 profile 文件中定义新的列表时,务必完整写出所需全部元素,避免漏掉默认值。
- 敏感信息加密处理:不建议将密码明文写在 application.properties,而是通过环境变量或 jasypt 等工具加解密。
搞清楚配置的加载顺序和覆盖规则,是防止“幽灵配置”影响线上稳定性的必修课。下一节我们将进入 Spring Boot 应用的打包与部署,将这些配置能力带入实际生产环境中。