Spring Boot 配置体系详解:YAML、加载顺序、Profile、属性注入与 Maven POM 整合
在 Spring Boot 项目中,配置看似只是一个 application.yml,实际却牵涉三件不同的事:配置文件从哪里被发现、同一个配置项由谁覆盖,以及最终如何注入 Java 对象。如果再引入 Maven 的 pom.xml 和多环境构建,很容易把 Maven Profile、Spring Profile、资源过滤和运行时参数混在一起。
本文以 Spring Boot 3.5.x + Java 21 + Maven 为例,从工程结构出发,将这些机制串起来,并给出能够直接套用的配置和代码。Spring Boot 2.3 及更早版本的配置文件加载机制与本文讨论的 Spring Boot 2.4+ Config Data 机制不同,阅读老教程时尤其要注意。
1. 先理解配置体系的三个层次
1 | |
这张图最需要记住的是:
| 层次 | 处理的问题 | 典型配置或命令 | 发生时间 |
|---|---|---|---|
| Maven 构建 | 依赖版本、构建插件、替换构建常量 | pom.xml、mvn -Pprod package、@project.version@ |
打包时 |
| Spring Boot 配置发现 | 找哪些配置文件、按什么位置加载 | application.yml、spring.config.* |
应用启动早期 |
| Spring Profile | 哪些环境配置生效 | application-dev.yml、--spring.profiles.active=dev |
应用启动时 |
| 属性源覆盖 | 多个来源定义同一 Key 时谁获胜 | 环境变量、-D、--app.name= |
配置解析时 |
| 配置值注入 | 将最终值提供给业务代码 | @Value、@ConfigurationProperties |
Bean 创建与绑定时 |
Maven 的 -Pprod 与 Spring Boot 的 --spring.profiles.active=prod 没有自动关联关系。 两者名称相同,也不会天然触发对方。这是多环境项目最常见的误区之一。
2. Maven 工程结构与基础 POM
建议先采用“单一可执行包 + 多环境运行时配置”的方式组织工程,而不是为 dev、test、prod 各编译一套完全不同的应用。
1 | |
2.1 使用 spring-boot-starter-parent 的 POM
以下是一个可作为起点的 pom.xml。其中 spring-boot-starter-validation 为后面 @ConfigurationProperties 的参数校验提供支持,spring-boot-configuration-processor 用于生成自定义配置项的元数据。
1 | |
spring-boot-starter-parent 除了管理依赖和插件默认值,还对 application.properties、application.yml 及相应的 Profile 文件提供合理的 Maven Resource Filtering 默认配置。后面会专门解释它为什么使用 @...@ 而不是 ${...}。
版本说明:示例固定为官方 Spring Boot 3.5.16 文档中的版本,以便配置规则和 POM 保持一致;它不是声称 Spring Boot 3.5 是所有项目都应升级到的最新主版本。
3. 配置文件的使用
3.1 配置文件介绍
Spring Boot 默认使用名称为 application 的配置文件,常见格式如下。
| 文件格式 | 示例 | 特点 | 建议 |
|---|---|---|---|
| Properties | application.properties |
扁平 Key-Value,便于小规模配置 | 项目配置少时可用 |
YAML(.yml) |
application.yml |
层级清晰,适合复杂对象和列表 | 一般业务项目推荐 |
YAML(.yaml) |
application.yaml |
与 .yml 同属 YAML 格式 |
与 .yml 二选一、保持统一 |
例如,下面两段配置在 Spring Boot 中表达相同的属性。
Properties:
1 | |
YAML:
1 | |
Spring Boot 最终会将 YAML 的层级属性展平为类似 spring.application.name、app.servers[0] 的 Key,再供 Environment 读取和配置绑定器使用。
**重要提醒:**如果同一位置同时存在 application.properties 和 application.yml,相同属性以 .properties 为准。建议在一个工程内统一格式,不要依赖双格式覆盖来管理环境。
3.2 YAML 基本语法
缩进、映射与列表
YAML 通过缩进表示层级;使用空格,不要使用 Tab 进行缩进。
1 | |
对应的部分属性路径为:
1 | |
标量、引号、空值、特殊字符
| 写法 | 说明 |
|---|---|
port: 8080 |
数值 |
enabled: true |
布尔值 |
name: "hello world" |
双引号字符串,可处理转义 |
pattern: 'a\\b' |
单引号字符串,反斜杠一般不作为转义符 |
password: "a#b:c" |
含特殊字符的值建议加引号 |
value: null |
空值,具体 Java 绑定结果取决于目标类型 |
items: [a, b, c] |
行内列表 |
labels: {team: dev, zone: sg} |
行内映射 |
text: | |
多行文本,保留换行 |
text: > |
多行文本,将多数换行折叠为空格 |
1 | |
日期、带前导零的值以及 on、off、yes、no 等容易引发不同 YAML 解析器类型推断差异的内容,最好显式加引号;不要假设所有 YAML 解析器对这些值都采用完全一致的规则。
多文档 YAML:---
一个 .yml 可以包含多个逻辑文档,通过 --- 分隔。后面的匹配文档可覆盖前面的属性。
1 | |
在 dev 环境,server.port=8081;在 prod 环境,server.port=9090;没有激活这两个 Profile 时使用 8080。多文档方式与拆分为 application-dev.yml、application-prod.yml 都可以,团队最好固定一种组织习惯。
注意,新版本应使用 spring.config.activate.on-profile 来条件激活 YAML 文档。不要直接照搬旧教程中的 spring.profiles: dev 写法。
3.3 配置文件的加载顺序:Spring Boot 默认搜索哪些路径?
Spring Boot 3.5 默认搜索以下位置。表格按照从低到高的优先级排列:同一属性在更靠下的位置出现时,通常可覆盖前面的值。
| 顺序 | 搜索位置 | 示例 |
|---|---|---|
| 1 | classpath:/ |
src/main/resources/application.yml |
| 2 | classpath:/config/ |
src/main/resources/config/application.yml |
| 3 | file:./ |
当前工作目录中的 application.yml |
| 4 | file:./config/ |
当前工作目录中的 config/application.yml |
| 5 | file:./config/*/ |
当前工作目录下 config/ 的直接子目录 |
这里的 ./ 指进程启动时的工作目录,并不一定是 JAR 所在目录。
假设目录如下:
1 | |
执行:
1 | |
Spring Boot 可以从 JAR 内的 classpath 和上述外部路径搜索配置。如果三处外部文件都定义同一个属性,具体生效值取决于相应位置的加载优先级;config/*/ 的多个子目录按绝对路径名称排序,后读取的相关配置可以覆盖先读取的配置。
这只是默认文件位置的搜索次序,不等同于所有 PropertySource 的优先级,也不能直接替代 Profile 文件的覆盖规则。
3.4 自定义文件位置与 Config Data 导入
从 Spring Boot 2.4 起,外部配置由 Config Data 机制处理。常见入口如下。
| 配置项 | 作用 | 是否保留默认搜索位置 |
|---|---|---|
spring.config.name |
修改配置文件的基础名称 | 是,搜索名称会变化 |
spring.config.location |
指定一组配置文件或目录 | 否,替换默认位置 |
spring.config.additional-location |
在默认位置之外增加位置 | 是 |
spring.config.import |
从现有配置再导入数据 | 是,按导入规则合并 |
修改基础文件名:
1 | |
增加外部配置位置(生产更常用):
1 | |
替换默认配置搜索位置:
1 | |
当路径是目录时,要以 / 结尾;Spring Boot 会在目录下追加配置文件名,并考虑适用的 Profile 变体。optional: 表示位置不存在时允许继续启动,适合可选配置;数据库凭据等必需配置不应该一律设置成可选。
这几个 spring.config.* 参数需要在加载配置之前就可用,因此应从命令行参数、JVM System Property 或操作系统环境变量等早期属性源提供;不要指望先读取某个普通配置文件,再由那个文件去决定同一轮初始搜索路径。
使用 spring.config.import:
1 | |
导入的属性对于触发导入的文档具有覆盖能力;多个导入按照声明顺序处理,后面的导入可覆盖前面的导入。同名文件不会因为多次声明而被反复导入。
在 Kubernetes 中,也可利用 configtree: 读取以文件名作为属性 Key 的挂载目录:
1 | |
例如 /run/secrets/db.password 这个文件可以提供 db.password 属性。敏感内容应交给合适的 Secret 管理机制,且需结合挂载目录权限控制,而不只是依赖配置文件格式。
进阶细节:
spring.config.location中逗号,分隔的位置组按顺序处理;分号;可以把多个位置放入同一 location group。在多个活动 Profile 的场景下,两种写法会影响覆盖顺序。没有这类复杂需求时,建议不要混用多组配置位置。
3.5 Profile 文件的加载
Profile 让同一套应用代码能够使用不同环境参数,例如数据库、Redis、日志级别或服务端口。
3.5.1 推荐的文件组织
1 | |
application.yml:
1 | |
application-dev.yml:
1 | |
application-prod.yml:
1 | |
激活 dev 后,server.port 是 8081、app.timeout 仍是 5s;激活 prod 后,分别是 9090、10s。未被覆盖的通用配置继续有效。
3.5.2 激活 Profile 的方式
| 方式 | 示例 | 适用场景 |
|---|---|---|
| 命令行参数 | java -jar app.jar --spring.profiles.active=prod |
直接运行 JAR |
| JVM 参数 | java -Dspring.profiles.active=prod -jar app.jar |
启动脚本 / JVM 参数管理 |
| OS 环境变量 | SPRING_PROFILES_ACTIVE=prod java -jar app.jar |
Docker / K8s / CI/CD |
| 基础配置文件 | spring.profiles.active: dev |
本地默认值(慎用于生产) |
| Maven 插件运行参数 | mvn spring-boot:run -Dspring-boot.run.profiles=dev |
开发时运行 |
注意 JVM -D 必须出现在 -jar 之前;--spring.profiles.active=... 是传给 Spring Boot 应用的命令行参数,通常放在 JAR 路径之后。
若没有显式激活任何 Profile,默认 Profile 名为 default,因此可能加载 application-default.yml。可以通过 spring.profiles.default 修改默认 Profile。
3.5.3 多 Profile、包含与分组
1 | |
对于多个活动 Profile,同一位置组内 Profile 专属配置一般遵循后面的 Profile 覆盖前面的 Profile:例如 dev,metrics 时,适用的 application-metrics.yml 可覆盖 application-dev.yml 中的同名属性。
也可以使用 Profile Group,在基础配置中定义:
1 | |
1 | |
这样会启用 prod-full,以及组内的 prod、observability、mysql。
另有 spring.profiles.include 可以添加 Profile,而不只是替换 spring.profiles.active 的值:
1 | |
限制非常重要:spring.profiles.active、spring.profiles.default、spring.profiles.include、spring.profiles.group 只能放在非 Profile 专属文档中,不能放进 application-prod.yml,也不能放进由 spring.config.activate.on-profile 激活的逻辑文档里,否则可能报配置校验异常。
3.5.4 @Profile 控制 Bean,而不只是文件
1 | |
以上示例的 PaymentClient、MockPaymentClient、RealPaymentClient 是项目中需要自行定义的接口和实现类,用于说明 @Profile 的条件注册语义,并非 Spring Boot 内置类。不要让生产 Bean 的安全边界只依赖配置文件名字,应该同时考虑部署与 Bean 的激活条件。
3.6 所有配置文件按以下顺序考虑:优先级从低到高
这一小节只讨论 Config Data 文件之间的优先级,暂时不加入命令行、环境变量、@TestPropertySource 等其它属性源。
Spring Boot 官方将常见的配置文件覆盖顺序归纳为以下四层:
| 优先级(低 → 高) | 配置文件来源 | 示例 |
|---|---|---|
| 1 | JAR 内的普通配置 | classpath:/application.yml |
| 2 | JAR 内的 Profile 专属配置 | classpath:/application-prod.yml |
| 3 | JAR 外的普通配置 | file:./application.yml |
| 4 | JAR 外的 Profile 专属配置 | file:./application-prod.yml |
这里最容易产生误解的是:外部普通配置的优先级可以高于 JAR 内的 Profile 专属配置。 不应该把所有 application-prod.yml 一概理解为高于所有 application.yml。
例如启动 prod 时存在:
| 文件 | app.name |
是否生效 |
|---|---|---|
JAR 内 application.yml |
jar-base |
被覆盖 |
JAR 内 application-prod.yml |
jar-prod |
被覆盖 |
外部 ./application.yml |
external-base |
被覆盖 |
外部 ./application-prod.yml |
external-prod |
最终生效 |
如果删除外部的 application-prod.yml,上述示例中最终将变成 external-base,而不是 jar-prod。
再补充三个限制条件:
- 同一位置、同一名称同时有
.properties和 YAML 格式时,.properties优先。 - 同一位置组中多个活动 Profile 遵循后者覆盖前者。 如果显式拆分了不同 location group,需要按照各组的处理顺序判断。
spring.config.import和自定义spring.config.location会改变简单的默认覆盖链。 不要把上面的四层表当成适用于所有自定义配置结构的绝对全序。
还要区分集合类型的覆盖规则。在 @ConfigurationProperties 绑定时:
1 | |
1 | |
最终绑定到 List<String> 的是 [node-c],不是 [node-a, node-b, node-c]。对于 Map/对象,则可以根据 Key 进行属性级合并和覆盖;具体行为仍取决于绑定目标类型。
3.7 外部属性读取:优先级从高到低
配置文件只是 Spring Environment 中一种 PropertySource。Spring Boot 3.5 官方文档列出了更完整的属性源优先级。下面按用户最常用的理解方向,从高到低列出;优先级高的属性源能够覆盖低的来源。
| 优先级(高 → 低) | PropertySource | 常见场景或说明 |
|---|---|---|
| 1 | Devtools 全局设置 | Devtools 生效时的 $HOME/.config/spring-boot 开发者全局属性;仅特殊开发场景 |
| 2 | @TestPropertySource |
测试类声明的测试属性 |
| 3 | @DynamicPropertySource |
测试时动态注入容器端口等 |
| 4 | 测试注解的 properties 属性 |
@SpringBootTest(properties = "...") |
| 5 | 应用命令行参数 | java -jar app.jar --server.port=9090 |
| 6 | SPRING_APPLICATION_JSON |
通过 JSON 传入的一组配置 |
| 7 | ServletConfig 初始化参数 |
Servlet 容器场景 |
| 8 | ServletContext 初始化参数 |
Servlet 容器场景 |
| 9 | JNDI 属性 | java:comp/env |
| 10 | JVM System Properties | java -Dserver.port=8082 -jar app.jar |
| 11 | 操作系统环境变量 | SERVER_PORT=8081 |
| 12 | RandomValuePropertySource |
仅提供 random.* 类属性 |
| 13 | Config Data | application.yml、application-prod.yml 等 |
| 14 | @PropertySource |
通过配置类引入的属性文件 |
| 15 | SpringApplication 默认属性 | SpringApplication.setDefaultProperties(...) |
这张表是官方完整规则的逆序呈现。平时开发主要记住这一段即可:
1 | |
中间省略了部分特殊 PropertySource;因此它是日常部署速记,而不是上一张完整表的严格简化替代。
3.7.1 做一次真实的覆盖实验
在 application.yml 中写:
1 | |
然后执行:
1 | |
最终 app.name=from-cli。如果去掉 --app.name=from-cli,就会得到 from-jvm;再去掉 -Dapp.name=from-jvm,才会得到 from-env。
3.7.2 环境变量命名与宽松绑定
推荐 YAML 中用小写短横线命名:
1 | |
对应环境变量常写成:
1 | |
映射规则是:点号 . 替换为下划线 _、短横线 - 删除、全部转为大写。因此 app.service.read-timeout 对应 APP_SERVICE_READTIMEOUT,不是机械地把每个短横线也替换成下划线。
对象列表也有下标语法,例如 app.clients[0].name 可通过 APP_CLIENTS_0_NAME 绑定。
3.7.3 两个经常忽略的细节
@PropertySource在 ApplicationContext 刷新阶段才加入Environment,对logging.*、spring.main.*等更早读取的参数可能来不及生效;也不能直接使用@PropertySource解析 YAML 文件。SPRING_APPLICATION_JSON虽然优先级较高,但其中的 JSONnull通常被属性解析器视为“属性不存在”,不能指望null清空低优先级来源的已有值。
4. 配置文件值注入
Spring Boot 常用的读取方式有三种:
| 方式 | 适合场景 | 特点 |
|---|---|---|
@Value |
少量简单属性 | 使用方便,支持 ${...} 和 SpEL |
Environment |
运行时查询属性、调试 | 可以按需访问,也能读取激活的 Profile |
@ConfigurationProperties |
一组相关的结构化业务配置 | 类型安全、宽松绑定、支持校验,优先推荐 |
4.1 配置文件占位符
这里一定要区分Spring 运行时占位符与Maven 构建期占位符。
| 语法 | 处理阶段 | 示例 | 谁来处理 |
|---|---|---|---|
${name} |
Spring 运行时 | ${DB_HOST:localhost} |
Spring Environment |
${name:default} |
Spring 运行时 | ${APP_TIMEOUT:5s} |
Spring 属性解析器 |
${random.uuid} |
Spring 运行时 | ${random.uuid} |
Spring Boot 的随机属性源 |
@project.version@ |
Maven 构建期 | @project.version@ |
Maven Resource Filtering |
@deploy.target@ |
Maven 构建期 | @deploy.target@ |
Maven Resource Filtering |
Spring 占位符:
1 | |
解释如下:
${APP_NAME:order-service}:优先读取APP_NAME,否则使用默认值order-service。${APP_TIMEOUT:5s}:默认值为5s,绑定到Duration后可直接操作时间类型。${random.uuid}:获取随机 UUID,适合非持久化的临时标识;不要用它替代稳定业务 ID。${DB_PASSWORD:}:允许默认空串,但生产数据库等关键凭据应配合强制校验或启动失败策略,不要默默使用空密码。
属性之间也可以互相引用:
1 | |
推荐在 Spring 占位符中使用规范的短横线形式(canonical kebab-case),例如 ${app.read-timeout},以便享受更一致的宽松查找规则。
Maven 占位符:
1 | |
打包时 Maven 将其替换为 POM 中的值,例如 1.0.0、springboot-config-demo、local。当资源过滤未开启或者该文件未被过滤时,它们只会作为普通字符串留在最终 JAR 中,Spring Boot 不会自动解释 @project.version@。
不建议把 Maven 资源过滤的定界符改回 ${...}。因为 Maven 和 Spring 都使用这套语法时,可能在构建阶段误处理原本应该保留到运行时的 ${DB_HOST} 等配置。
4.2 使用 @Value 注入少量配置
1 | |
@Value 适合小范围配置,不适合在几十个类中重复注入一大组配置项。复杂配置建议采用下一种方式。
4.3 @ConfigurationProperties:类型安全配置绑定
先准备 application.yml:
1 | |
在 Java 21 中可以直接使用 record:
1 | |
然后在启动类中启用配置扫描:
1 | |
这样一来:
- Spring 根据
app.*完成结构化绑定,并将结果注册为 Bean。 Duration、URI、List<URI>可以自动转换,不需要手动解析字符串。@NotBlank、@NotNull会校验配置,缺少必要值时尽早暴露错误。record使用单构造器绑定;此类不应再额外标记为普通@Component。可通过@ConfigurationPropertiesScan或@EnableConfigurationProperties注册。
如果没有使用 Java record,也可以采用具有 getter/setter 的 JavaBean 方式。二者都属于 @ConfigurationProperties,只是绑定模型不同。
@ConfigurationProperties 与 @Value 的区别
| 功能 | @Value |
@ConfigurationProperties |
|---|---|---|
| 单值读取 | 很方便 | 可以,但略重 |
| 嵌套对象绑定 | 不适合 | 原生支持 |
| List / Map 绑定 | 需自行处理或做表达式转换 | 原生支持 |
| 类型转换 | 支持常见转换 | 结构化绑定更完整 |
| 宽松绑定 | 部分支持,建议规范命名 | 完整支持 |
| JSR/Jakarta Bean Validation | 通常自行配合处理 | 可直接配合 @Validated |
| SpEL 表达式 | 支持 | 不支持 |
| 配置元数据和 IDE 提示 | 无 | 可借助 configuration processor |
4.4 使用 Environment 动态读取
1 | |
适合框架组件、诊断功能或按需读取;普通业务配置则更建议一次性绑定为配置对象,不要在业务代码中到处散落字符串 Key。
5. Spring Boot 配置与 Maven pom.xml 整合
5.1 Maven Profile 与 Spring Profile 到底有什么区别?
| 维度 | Maven Profile | Spring Boot Profile |
|---|---|---|
| 激活命令 | mvn -Pprod package |
--spring.profiles.active=prod |
| 执行时机 | 构建时 | 运行时 |
| 控制对象 | 依赖、插件、构建属性、资源过滤 | 配置数据、Bean 的环境化激活 |
| 是否需要重新打包 | 取决于构建内容;通常是 | 通常不用 |
| 是否自动互相联动 | 不会 | 不会 |
建议:同一份 JAR 在不同环境运行,环境差异通过 Spring Profile、外部配置、环境变量或 Secret 注入。Maven Profile 更适合控制构建元数据、测试策略、构建插件等。这样可以减少“测试用的包和生产用的包不是同一个包”的差异。
5.2 利用 POM 资源过滤写入版本与构建信息
在上面的 POM 中已配置:
1 | |
src/main/resources/application.yml:
1 | |
在使用 spring-boot-starter-parent 默认资源过滤规则的情况下,运行:
1 | |
target/classes/application.yml 的对应内容会在构建时变成类似:
1 | |
构建期填写软件版本、Git 提交信息或构建标识很合理;不要把生产数据库密码、访问令牌或其它长期敏感信息烘焙到 JAR 里。
5.3 在 POM 中使用 Maven Profile
可以给 POM 增加以下片段(放在 <project> 下,与 <build> 同级):
1 | |
运行:
1 | |
会把构建元数据 build.target 替换为 production,但这不会自动激活 Spring 的 prod Profile。如果你只是希望切换运行环境,正确做法通常是:
1 | |
5.4 开发阶段通过 Maven 插件启动指定环境
1 | |
这里的 spring-boot.run.profiles 是 Spring Boot Maven Plugin 的参数,不是 Maven 自身的 Profile ID。
5.5 自定义 Parent POM 时,如何显式配置资源过滤?
如果项目使用公司内部 Parent POM,仅引入了 spring-boot-dependencies BOM 而没有继承 spring-boot-starter-parent,就不能假定资源过滤、编译参数及 Maven 插件执行配置全部自动继承。
可在子模块的 <build> 中使用类似配置(以下为示意,需与企业父 POM 的插件管理合并核对):
1 | |
上面的资源过滤配置仅针对 application* 文件且使用 @ 定界符;POM 属性仍使用 Maven 自身的 ${...} 语法。与企业已有 Parent 合并时,建议通过 mvn help:effective-pom 检查最终插件和 resources 配置,而不是重复覆盖已经存在的设置。
5.6 是否应该在 POM 中写 spring.profiles.active?
技术上可以利用 Maven 资源过滤,将:
1 | |
打包成一个带默认 Profile 的配置文件,但不推荐以此作为生产环境切换机制。因为:
-Pprod是构建时决定的,增加了构建产物的环境耦合。- 同一份 JAR 无法自然地在测试和生产之间迁移(虽然运行时仍可用更高优先级属性覆盖)。
deploy.target之类的 Maven 构建名称不一定就是实际 Spring Profile 名称。- 容器和平台部署通常更适合在运行时传入
SPRING_PROFILES_ACTIVE。
更合理的设计是:Maven 生成版本信息,Spring Boot 根据运行环境生成最终配置。
6. 一个从打包到部署的完整实践
6.1 基础配置与环境差异
application.yml:
1 | |
application-dev.yml:
1 | |
application-prod.yml:
1 | |
此时,前面定义的 ConfigDemoApplication、AppProperties、SimpleConfigReader 可以直接使用。默认 POM 不依赖任何数据库即可运行这个演示项目。
6.2 打包并验证资源过滤
1 | |
确保 build.version 已经变成 1.0.0,而 ${APP_NAME:order-service} 仍原样保留,等待 Spring Boot 在运行阶段处理。
6.3 同一份 JAR 分别启动两种环境
1 | |
第二个命令中:
| 属性 | 默认配置 | Profile 配置 | 额外运行时属性 | 最终值 |
|---|---|---|---|---|
server.port |
8080 |
9090 |
无 | 9090 |
app.name |
order-service(默认值) |
order-service-prod |
CLI order-service-online |
order-service-online |
app.timeout |
5s(默认值) |
10s |
OS 环境变量 APP_TIMEOUT=12s |
12s |
build.version |
Maven 替换为 1.0.0 |
无 | 无 | 1.0.0 |
这个例子同时覆盖了 Maven 资源过滤 → Spring 配置文件 → Profile 覆盖 → 环境变量覆盖 → 命令行覆盖 → Java 对象绑定 的完整过程。
6.4 生产环境的配置目录建议
1 | |
或者使用独立的配置目录:
1 | |
1 | |
这样可以在不修改 JAR 的情况下调整外部配置。对于 Kubernetes,则通常把非敏感配置放进 ConfigMap,把敏感数据交给 Secret / 专用密钥管理系统,并通过环境变量或挂载文件注入。
7. 常见问题与排查方式
| 现象 | 常见原因 | 排查建议 |
|---|---|---|
修改 application.yml 不生效 |
被外部文件、Profile、环境变量或 CLI 覆盖 | 按属性源优先级从高往低查 |
application-prod.yml 不生效 |
未激活 prod 或路径不正确 |
检查启动参数及 Active Profiles |
mvn -Pprod package 后没有进入生产环境 |
Maven Profile 不等于 Spring Profile | 运行时显式指定 spring.profiles.active |
@project.version@ 原样输出 |
Maven 没有过滤该资源 | 检查 Parent POM、resources、target/classes |
${APP_NAME} 无法解析 |
没有设置环境变量、也没有默认值 | 设置环境变量或声明合理默认值 |
@Value 找不到配置项 |
Key 拼写、Bean 管理方式、配置加载时机有误 | 使用规范 Key,确认是 Spring Bean |
| 配置属性类没有注册成 Bean | 少了 @ConfigurationPropertiesScan / @EnableConfigurationProperties |
检查扫描路径及注册方式 |
@PropertySource 无法加载 YAML |
默认注解不支持 YAML | 使用 Config Data 或自定义 PropertySourceFactory |
| 明明只改一个 List 元素却整个 List 变了 | List 是整体覆盖,不是拼接 | 在高优先级配置中提供完整列表 |
| 启动时报找不到配置位置 | 自定义配置路径不存在 | 检查绝对路径、工作目录和 optional: |
spring.profiles.active 报无效属性 |
写在 Profile 专属文档内 | 移到基础非 Profile 文档或启动参数 |
排查时可以显式打开配置加载相关日志:
1 | |
这有助于观察 Config Data 的解析与加载过程。日志可能非常详细,生产环境不要长期启用。
此外,Spring Boot Actuator 的 /actuator/env 和 /actuator/configprops 能帮助定位属性来源、绑定情况,但它们可能暴露系统配置结构;不要在公网或未授权环境开放这些诊断端点。必要时仅在受控环境短时启用,并启用适当认证、网络访问控制和敏感字段脱敏。
排查 Maven 构建则可使用:
1 | |
8. 最终总结:一张表记住配置覆盖与 Maven 整合
| 需求 | 推荐方式 | 不推荐的混淆点 |
|---|---|---|
| 统一默认配置 | JAR 内 application.yml |
把全部环境参数写死 |
| 开发/测试/生产差异 | application-{profile}.yml |
误认为 Maven -P 会自动激活 |
| 生产环境参数覆盖 | 外部配置文件、环境变量、Secret | 为每个环境复制一份业务代码 |
| 临时覆盖单个参数 | 应用命令行 --key=value |
把 JVM -D 写在 -jar 后面 |
| 指定附加配置目录 | spring.config.additional-location |
错用 spring.config.location 丢失默认位置 |
| 代码读取一个属性 | @Value |
在配置繁多的系统里滥用 |
| 代码读取一组属性 | @ConfigurationProperties + 校验 |
手动拼接并解析大量字符串 |
| POM 的版本号写入配置 | Maven @project.version@ |
与 Spring ${...} 占位符混淆 |
| 同包多环境部署 | 同一 JAR + 运行时 Profile | 依赖打包时写死 spring.profiles.active |
| 诊断最终属性值 | Config Data TRACE、受控 Actuator | 只看单个 application.yml 就认定最终值 |
可以将 Spring Boot 配置机制归纳为一个公式:
1 | |
这不是数学加法,而是一个先构建、再发现、再覆盖、最后绑定的过程。真正理解它后,无论是本地开发、JAR 包部署、Docker、Kubernetes,还是 SaaS 多环境服务,排查配置问题时都会有一个稳定且可验证的思路。
参考资料
- Spring Boot 3.5 — Externalized Configuration:属性源完整优先级、配置搜索位置、Config Data、占位符、YAML 与类型安全绑定。
- Spring Boot 3.5 — Profiles:活动 Profile、默认 Profile、include、group 与 Profile 专属文件。
- Spring Boot 3.5 — Maven Plugin: Using the Plugin:Parent POM、资源过滤、
@...@占位符以及spring-boot.run.profiles。 - Apache Maven — Introduction to Build Profiles:Maven
-P、POM Profile 的激活机制与构建配置覆盖。 - Spring Boot 3.5 — Properties and Configuration:多文档配置与属性配置实践。