配置属性优先级
一句话定位:Spring Boot 的 17 级属性源优先级 是一套从高到低的配置覆盖规则,确保命令行参数、环境变量等高优先级来源能够覆盖打包在 JAR 内的默认配置,实现"同一份代码、不同环境运行"。
定义与作用
配置属性优先级 规定了当多个配置来源同时存在同名属性时,哪个来源的值最终生效。这是外部化配置的核心机制——没有优先级规则,"配置与代码分离"就只是一句空话。
Spring Boot 的优先级规则遵循**"越靠近运行时,优先级越高"**的原则:
- 命令行参数(启动时传入)优先级最高
- 环境变量次之
- 打包在 JAR 内的
application.yml优先级最低
对比传统 SSM 的繁琐过程
| 维度 | 传统 SSM | Spring Boot 优先级机制 |
|---|---|---|
| 配置覆盖 | 无统一规则,靠 XML 加载顺序或手动合并 | 17 级明确优先级,自动覆盖 |
| 运维介入 | 需要修改 XML 文件并重新打包 | 只需传命令行参数或环境变量 |
| 问题排查 | 不知道哪个配置最终生效 | 通过 /actuator/env 端点可查看实际生效值 |
| 默认值 | 代码中硬编码或 XML 中写死 | 低优先级配置源提供默认值,高优先级覆盖 |
适用位置与常用属性
17 级优先级全景表
| 优先级 | 属性来源 | 覆盖能力 | 示例 |
|---|---|---|---|
| 1 | 命令行参数 | 最高 | --server.port=9090 |
| 2 | SPRING_APPLICATION_JSON(环境变量) | 极高 | SPRING_APPLICATION_JSON={"server":{"port":9090}} |
| 3 | JNDI 属性 | 高 | java:comp/env/spring.datasource.url |
| 4 | ServletConfig 初始化参数 | 高 | web.xml 中的 <init-param> |
| 5 | ServletContext 初始化参数 | 高 | web.xml 中的 <context-param> |
| 6 | System.getProperties()(系统属性) | 高 | -Dserver.port=9090 |
| 7 | System.getenv()(环境变量) | 高 | SERVER_PORT=9090 |
| 8 | application-{profile}.properties(./config 子目录) | 中 | ./config/application-dev.yml |
| 9 | application-{profile}.properties(当前目录) | 中 | ./application-dev.yml |
| 10 | application.properties(./config 子目录) | 中 | ./config/application.yml |
| 11 | application.properties(当前目录) | 中 | ./application.yml |
| 12 | application-{profile}.properties(classpath config) | 低 | classpath:config/application-dev.yml |
| 13 | application-{profile}.properties(classpath) | 低 | classpath:application-dev.yml |
| 14 | application.properties(classpath config) | 低 | classpath:config/application.yml |
| 15 | application.properties(classpath) | 最低 | classpath:application.yml |
| 16 | @PropertySource 注解导入 | 兜底 | @PropertySource("classpath:custom.yml") |
| 17 | SpringApplication.setDefaultProperties() | 最后兜底 | 代码中硬编码默认值 |
注意:Spring Boot 2.4 之前
spring.profiles的优先级略有不同,本教程基于 Spring Boot 2.7.18。
核心原理
17 级优先级层次图
图解释:上图从上到下展示优先级从高到低。运行时层(命令行、环境变量、系统属性)永远覆盖配置文件层;Profile 层的配置文件(application-dev.yml)覆盖同位置的非 Profile 配置文件;默认层(@PropertySource、代码默认值)只在所有上层都未配置时生效。
配置合并与覆盖机制
核心原理:ConfigurableEnvironment 内部维护一个有序的 PropertySource 列表,查询属性时按顺序遍历,第一个匹配的值立即返回。这意味着高优先级的来源永远覆盖低优先级的同名属性。
完整示例
场景说明
飞翔科技的学生成绩管理系统需要同时满足以下场景:
- 开发环境:端口 8080,H2 内存数据库,调试模式开启
- 测试环境:端口 8081,MySQL 测试库,调试模式关闭
- 生产环境:端口 80,MySQL 生产库,调试模式关闭
架构师白歌要求:同一份 JAR 包通过外部配置覆盖内部默认值,不能为每个环境单独打包。
后端开发小崔决定在 application.yml(classpath)中放默认值,在 JAR 外部 config/application.yml 放测试环境覆盖值,在启动命令行中传入生产环境参数。
操作前:所有环境共用一份配置,经常搞错
# 操作前:只有一个 application.yml,没有区分环境
server:
port: 8080
spring:
datasource:
url: jdbc:h2:mem:student_db
driver-class-name: org.h2.Driver
feixiang:
student:
debug-mode: true
痛点:
- 小崔把测试环境的配置误提交到代码仓库,导致生产环境用了测试数据库
- 每次切换环境都要手动改
application.yml重新打包 - 运维无法在不改代码的情况下调整配置
使用优先级机制的完整代码
步骤1:JAR 内部默认配置(classpath:application.yml)—— 优先级15
# src/main/resources/application.yml(dev 默认值)
server:
port: 8080
spring:
application:
name: student-system
datasource:
url: jdbc:h2:mem:student_db;DB_CLOSE_DELAY=-1
driver-class-name: org.h2.Driver
username: sa
password:
feixiang:
student:
system-name: "飞翔科技学生成绩管理系统"
debug-mode: true
max-upload-size: 10MB
步骤2:JAR 外部覆盖配置(./config/application.yml)—— 优先级10
在部署目录创建 config/application.yml:
# ./config/application.yml(测试环境覆盖)
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://test-db:3306/student_db?useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: test_user
password: test_pass
feixiang:
student:
debug-mode: false
步骤3:命令行参数最终覆盖(启动时传入)—— 优先级1
# 生产环境启动,命令行参数覆盖一切
java -jar student-system.jar \
--server.port=80 \
--spring.datasource.url=jdbc:mysql://prod-db:3306/student_db?useSSL=true \
--spring.datasource.username=prod_user \
--spring.datasource.password=prod_secure_pass \
--feixiang.student.debug-mode=false
步骤4:读取配置的服务类
package com.feixiang.student.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class EnvironmentCheckService {
@Value("${server.port}")
private int serverPort;
@Value("${spring.datasource.url}")
private String datasourceUrl;
@Value("${feixiang.student.debug-mode}")
private boolean debugMode;
public void printEffectiveConfig() {
System.out.println("=== 当前生效配置 ===");
System.out.println("端口: " + serverPort);
System.out.println("数据库: " + datasourceUrl);
System.out.println("调试模式: " + debugMode);
}
}
操作后运行结果及分析
场景A:直接启动(无外部配置,仅 classpath 默认值)
=== 当前生效配置 ===
端口: 8080
数据库: jdbc:h2:mem:student_db;DB_CLOSE_DELAY=-1
调试模式: true
场景B:在 JAR 外部 config/application.yml 存在时启动
=== 当前生效配置 ===
端口: 8081 ← 被 ./config/application.yml 覆盖
数据库: jdbc:mysql://test-db:3306/student_db?useSSL=false ← 被覆盖
调试模式: false ← 被覆盖
场景C:命令行参数启动(生产环境)
=== 当前生效配置 ===
端口: 80 ← 被 --server.port=80 覆盖
数据库: jdbc:mysql://prod-db:3306/student_db?useSSL=true ← 被覆盖
调试模式: false ← 被覆盖
分析:
- 优先级10覆盖15:
./config/application.yml的优先级(10)高于classpath:application.yml(15),所以测试环境配置生效 - 优先级1覆盖一切:命令行参数
--server.port=80优先级(1)高于所有配置文件,所以生产环境端口被覆盖为 80 - 未覆盖的配置保留:如果
./config/application.yml中未定义feixiang.student.system-name,则使用classpath:application.yml中的默认值 "飞翔科技学生成绩管理系统" - 运维零代码介入:运维人员只需在服务器上放置配置文件或修改启动脚本,无需改代码
易错场景与面试考点
易错场景一:误以为配置文件位置不重要
小崔在测试服务器上把 application.yml 放在 JAR 包同级目录,但 JAR 内部的 application.yml 仍然生效:
# 目录结构
/opt/apps/
├── student-system.jar
└── application.yml # ← 小崔放在这里
后果:Spring Boot 的加载顺序中,JAR 外部的 ./application.yml(优先级11)确实高于 classpath:application.yml(优先级15)。但小崔不知道 config/application.yml(优先级10)比 ./application.yml(优先级11)更高。运维同时放了 config/application.yml,导致小崔的配置被覆盖。
正确做法:明确区分三种外部位置的优先级:./config/ > ./ > classpath。在团队协作中约定统一的配置放置位置,避免多层覆盖导致困惑。
易错场景二:环境变量名写错导致覆盖失败
小崔想在 Linux 服务器上通过环境变量覆盖数据库 URL:
# 错误示范:环境变量命名不符合松散绑定
export spring.datasource.url=jdbc:mysql://prod-db:3306/student_db
后果:Shell 环境变量名中包含点号(.)在某些环境中不合法,且 Spring Boot 的 SystemEnvironmentPropertySource 期望使用下划线替代点号。该环境变量未被 Spring Boot 识别,应用仍使用 application.yml 中的默认值。
正确做法:环境变量使用全大写,点号替换为下划线:
export SPRING_DATASOURCE_URL=jdbc:mysql://prod-db:3306/student_db
export FEIXIANG_STUDENT_DEBUG_MODE=false
面试考点
Q:Spring Boot 的配置属性优先级是什么?列举前5级。
从高到低:1. 命令行参数
--key=value;2.SPRING_APPLICATION_JSON环境变量;3. JNDI 属性;4.ServletConfig初始化参数;5.ServletContext初始化参数;6. 系统属性-Dkey=value;7. 环境变量。核心原则是越靠近运行时,优先级越高。
Q:为什么命令行参数优先级最高?
命令行参数是启动时最后传入的,由运维或部署脚本控制。最高优先级确保运维可以在不修改代码、不修改配置文件的情况下,临时调整应用行为(如紧急切换端口或关闭某个功能)。
Q:如何查看当前生效的配置值?
引入
spring-boot-starter-actuator,访问/actuator/env端点,可以看到所有PropertySource以及每个属性的实际生效值和来源。此外,/actuator/configprops可以看到所有@ConfigurationProperties的绑定结果。
Q:多个配置文件同时存在时,Spring Boot 如何合并?
不是"合并内容",而是按优先级覆盖同名属性。每个
PropertySource是一个独立的键值集合,查询时按优先级从高到低遍历,第一个匹配的值即返回。不同属性可以共存(如server.port在命令行参数中,spring.datasource.url在application.yml中)。
Q:application.yml 和 application.properties 同时存在时,哪个生效?
如果同名文件(同路径)同时存在
.yml和.properties,Spring Boot 2.4 之前的版本是.properties覆盖.yml;2.4 之后改为.yml覆盖.properties。但最佳实践是只使用一种格式,避免混淆。本教程统一使用 YAML。