配置的消费端(@Value@ConfigurationProperties 怎么用)见常用注解,Environment 在启动流程中的位置见启动原理。本文讲来源端:配置从哪来、谁覆盖谁、绑定规则是什么。代码基于 Spring Boot 4.x——配置体系自 2.4 定型后未再变动,3.x/4.x 通用。

Environment 抽象:一条有序链表

Spring 把所有配置来源统一抽象为 PropertySource:配置文件、环境变量、系统属性、命令行参数,各自包装成一个 PropertySource,按优先级排成一条有序链表,挂到 Environment 上。查询语义只有一条规则:

Environment.getProperty(key) 从链表头开始遍历,先查到的赢

优先级没有独立的规则引擎——链表顺序就是优先级。启动后可以直接验证:

1
2
3
4
5
6
7
8
9
@Component
class PropertySourceProbe {

PropertySourceProbe(ConfigurableEnvironment env) {
// 按优先级从高到低打印全部来源名
env.getPropertySources().forEach(
ps -> System.out.println(ps.getName()));
}
}

理解这条链表,下文所有覆盖行为都是它的推论。

配置来源与优先级

官方文档列出十几级来源,工程上只需记住这张精简表(高 → 低):

优先级来源示例
1命令行参数--server.port=9001
2Java 系统属性-Dserver.port=9001
3OS 环境变量SERVER_PORT=9001
4profile 配置文件application-prod.yml
5主配置文件application.yml
6@PropertySource注解标注的额外文件
7代码默认值setDefaultProperties / 绑定默认

配置来源优先级链:从命令行参数到代码默认值共七级,高优先级来源在链表中靠前、先查先赢;图中标注了 jar 外配置覆盖 jar 内、profile 文件覆盖主文件的两条次级规则。

两条记忆规则覆盖全部细节:

  • 越外越高:命令行 > 系统属性 > 环境变量 > jar 外文件 > jar 内文件。离代码越远、离部署越近的来源越优先——改配置不需要重新打包。
  • 越具体越高:profile 文件 > 主文件;同一文件内,后面声明的属性覆盖前面的。

测试场景另有 @TestPropertySource@SpringBootTest(properties = ...) 两级,插在整个链表最前面,不影响生产语义。

配置文件的加载

application.yml 按以下位置扫描,后加载的覆盖先加载的(低 → 高):

  1. classpath:/
  2. classpath:/config/
  3. 当前目录 ./
  4. 当前目录 ./config/
  5. 当前目录 ./config/*/(每个子目录)

同位置同时存在 .properties.yml.properties 优先。两个格式混用没有收益,只留 .yml

需要引入扫描位置之外的文件,用 spring.config.import 显式声明:

1
2
3
4
5
6
7
spring:
config:
import:
# optional 前缀允许文件不存在
- optional:file:./local-overrides.yml
# 目录整体导入:每个文件名为 key,内容为值
- configtree:/etc/myapp/

configtree 直接对齐 K8s 挂载 ConfigMap/Secret 的目录结构,是容器环境注入配置的标准入口。

Profile 机制

profile 是"按环境叠加配置"的机制,不是"切换配置文件"。主文件总是加载,激活的 profile 文件在其上叠加并覆盖同名属性。

激活方式(三种等价,按优先级表生效):

1
2
3
java -jar app.jar --spring.profiles.active=prod
java -jar app.jar -Dspring.profiles.active=prod
SPRING_PROFILES_ACTIVE=prod java -jar app.jar

多 profile 用逗号分隔,后面的覆盖前面的--spring.profiles.active=common,prod 中 prod 的属性覆盖 common。这个顺序语义经常被忽略,两个 profile 定义了同名 key 时结果就是反直觉的。

yml 单文件内可用多文档块内联 profile,与拆成 application-prod.yml 完全等价:

1
2
3
4
5
6
7
8
9
10
# 主文档:所有环境共享的默认值
server:
port: 8080
---
spring:
config:
activate:
on-profile: prod
server:
port: 80

一组 profile 总是同时激活时,用 group 收敛为一个名字:

1
2
3
4
spring:
profiles:
group:
prod: proddb, prodmq

--spring.profiles.active=prod 实际激活 proddb 与 prodmq,且组内顺序保留"后覆盖前"语义。

绑定机制

Relaxed Binding

@ConfigurationProperties 绑定时不要求 key 精确匹配,四种写法等价:

来源写法绑定到
yml / propertiesapp.main-name(推荐 kebab-case)appProperties.mainName
yml / propertiesapp.mainName同上
系统属性-Dapp.main-name=x同上
环境变量APP_MAIN_NAME同上

环境变量的换算规则:点转下划线、全大写、去掉连字符。这是部署层覆盖任意配置项的通用通道——不用改任何文件,APP_MAIN_NAME=xyz 即可覆盖 app.main-name

注意边界:relaxed binding 只作用于 @ConfigurationProperties@Value("${app.mainName}") 走占位符解析,key 必须与环境中的属性名精确一致

构造器绑定与校验

绑定目标首选 record:不可变、构造器绑定、配合 @Validated 在启动期校验:

1
2
3
4
5
6
7
8
9
@Validated
@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(

@NotBlank String host,

@Min(1) @Max(65535) int port,

Duration timeout) { }

字段缺失或越界时应用在启动期直接失败,附字段名与来源定位——比运行期空指针早暴露一个生命周期。

@Value 的三个边界

  • 无 relaxed binding,key 必须精确;
  • 无校验能力,拼错 key 只能靠默认值兜底:@Value("${app.mail.host:localhost}")
  • 标量散值用它可以,成组配置必须走 @ConfigurationProperties——两者的选择标准见常用注解

多环境管理实践

主文件放默认值,profile 文件只放差异。 application.yml 承载所有环境共享的配置与本地开发默认值;application-prod.yml 只写差异项——端口、数据源地址、特性开关。review 配置变更时 diff 出来的就是环境差异本身。

敏感配置不进仓库。 密码、密钥通过环境变量或 configtree 注入,由部署平台(K8s Secret、CI 变量)保管。代码库里出现敏感信息即视为泄露,轮换与清理 Git 历史比"下次注意"便宜。

一次构建,多处部署。 jar 构建产物在所有环境相同,环境差异全部来自外部配置——这是优先级设计"越外越高"的意图,也是 12-Factor 的配置原则。为每个环境打一个包等于放弃了整条优先级链。

常见坑

profile 文件里声明 spring.profiles.active:2.4 起直接启动失败,抛 InvalidConfigDataPropertyException。profile 文件只能由外部激活,不能自我激活。

环境变量名写错server.port 对应 SERVER_PORT,写成 SERVER.PORTserver_port 都不会绑定。换算规则只有一条:点转下划线再全大写。

改了 jar 内配置不生效:部署目录存在同名 jar 外配置文件,覆盖了 jar 内的修改。排查先看 ./config/./ 下有没有遗留文件。

占位符在 @PropertySource 文件中找不到 key@PropertySource 加载的属性不参与 @ConfigurationProperties 的 relaxed binding,且在 Environment 链表中位置靠后(第 6 级),容易被主配置文件覆盖。

yml 与 properties 同存:同位置 .properties 优先于 .yml,两个文件各改一处,生效的是 properties——症状是"改了 yml 没反应"。

检查清单

检查项标准
格式只用 yml,不与 properties 混用
分层主文件放默认值与共享项,profile 文件只放差异
覆盖临时覆盖用命令行或环境变量,不改文件
绑定成组配置用 @ConfigurationProperties + record + @Validated
敏感项环境变量 / configtree 注入,不进仓库
profile用 group 收敛组合;不在 profile 文件里激活 profile

相关阅读