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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
                  构建阶段(Maven)                         运行阶段(Spring Boot)

pom.xml ── Maven Profile ── resources filtering ──▶ 构建产物 JAR
│
▼
┌────────────────────────── 配置发现 ──────────────────────────┐
│ classpath:/ classpath:/config/ ./ ./config/ ./config/*/ │
│ spring.config.location / additional-location / import │
└──────────────────────────────┬──────────────────────────────┘
▼
Profile 激活与文件合并
▼
Environment / PropertySources(属性优先级)
▼
@Value / Environment / @ConfigurationProperties
▼
Spring Bean

这张图最需要记住的是:

层次 处理的问题 典型配置或命令 发生时间
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
springboot-config-demo/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/com/example/configdemo/
│ │ │ ├── ConfigDemoApplication.java
│ │ │ ├── config/AppProperties.java
│ │ │ └── web/SimpleConfigReader.java
│ │ └── resources/
│ │ ├── application.yml
│ │ ├── application-dev.yml
│ │ ├── application-test.yml
│ │ └── application-prod.yml
│ └── test/
│ └── java/
└── target/

2.1 使用 spring-boot-starter-parent 的 POM

以下是一个可作为起点的 pom.xml。其中 spring-boot-starter-validation 为后面 @ConfigurationProperties 的参数校验提供支持,spring-boot-configuration-processor 用于生成自定义配置项的元数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.16</version>
<relativePath/>
</parent>

<groupId>com.example</groupId>
<artifactId>springboot-config-demo</artifactId>
<version>1.0.0</version>
<name>springboot-config-demo</name>
<description>Spring Boot Configuration Demonstration</description>

<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 自定义构建元数据,默认不代表 Spring 的活动 Profile -->
<deploy.target>local</deploy.target>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

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
2
3
4
5
server.port=8080
spring.application.name=order-service
app.timeout=5s
app.servers[0]=https://node-1.example.com
app.servers[1]=https://node-2.example.com

YAML:

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8080

spring:
application:
name: order-service

app:
timeout: 5s
servers:
- https://node-1.example.com
- https://node-2.example.com

Spring Boot 最终会将 YAML 的层级属性展平为类似 spring.application.name、app.servers[0] 的 Key,再供 Environment 读取和配置绑定器使用。

**重要提醒:**如果同一位置同时存在 application.properties 和 application.yml,相同属性以 .properties 为准。建议在一个工程内统一格式,不要依赖双格式覆盖来管理环境。

3.2 YAML 基本语法

缩进、映射与列表

YAML 通过缩进表示层级;使用空格,不要使用 Tab 进行缩进。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
app:
name: order-service
enabled: true
retry-count: 3
timeout: 5s

# YAML 列表
servers:
- https://node-1.example.com
- https://node-2.example.com

# 对象列表
databases:
- name: primary
host: 10.0.0.10
port: 3306
- name: replica
host: 10.0.0.11
port: 3306

# Map
labels:
team: finance
region: ap-southeast-1

对应的部分属性路径为:

1
2
3
4
5
app.name=order-service
app.servers[0]=https://node-1.example.com
app.databases[0].name=primary
app.databases[1].host=10.0.0.11
app.labels.team=finance

标量、引号、空值、特殊字符

写法 说明
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
2
3
4
5
6
7
8
9
10
11
app:
description: |
订单配置示例。
第二行仍保留换行。

message: >
这段较长的内容
通常会被折叠为一行。

version-text: "01:02:03"
switch-text: "on"

日期、带前导零的值以及 on、off、yes、no 等容易引发不同 YAML 解析器类型推断差异的内容,最好显式加引号;不要假设所有 YAML 解析器对这些值都采用完全一致的规则。

多文档 YAML:---

一个 .yml 可以包含多个逻辑文档,通过 --- 分隔。后面的匹配文档可覆盖前面的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
server:
port: 8080

app:
name: base

---
spring:
config:
activate:
on-profile: dev

server:
port: 8081

app:
name: development

---
spring:
config:
activate:
on-profile: prod

server:
port: 9090

app:
name: production

在 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
2
3
4
5
6
7
/opt/order-service/
├── order-service.jar
├── application.yml
└── config/
├── application.yml
└── redis/
└── application.yml

执行:

1
2
cd /opt/order-service
java -jar order-service.jar

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
2
java -jar order-service.jar --spring.config.name=order
# 例如查找 order.yml、order-prod.yml 等

增加外部配置位置(生产更常用):

1
2
3
java -jar order-service.jar \
--spring.profiles.active=prod \
--spring.config.additional-location=optional:file:/etc/order-service/

替换默认配置搜索位置:

1
2
java -jar order-service.jar \
--spring.config.location=optional:classpath:/,optional:file:/etc/order-service/

当路径是目录时,要以 / 结尾;Spring Boot 会在目录下追加配置文件名,并考虑适用的 Profile 变体。optional: 表示位置不存在时允许继续启动,适合可选配置;数据库凭据等必需配置不应该一律设置成可选。

这几个 spring.config.* 参数需要在加载配置之前就可用,因此应从命令行参数、JVM System Property 或操作系统环境变量等早期属性源提供;不要指望先读取某个普通配置文件,再由那个文件去决定同一轮初始搜索路径。

使用 spring.config.import:

1
2
3
4
5
spring:
config:
import:
- optional:file:./config/redis.yml
- optional:file:./config/mysql.yml

导入的属性对于触发导入的文档具有覆盖能力;多个导入按照声明顺序处理,后面的导入可覆盖前面的导入。同名文件不会因为多次声明而被反复导入。

在 Kubernetes 中,也可利用 configtree: 读取以文件名作为属性 Key 的挂载目录:

1
2
3
spring:
config:
import: "optional:configtree:/run/secrets/"

例如 /run/secrets/db.password 这个文件可以提供 db.password 属性。敏感内容应交给合适的 Secret 管理机制,且需结合挂载目录权限控制,而不只是依赖配置文件格式。

进阶细节:spring.config.location 中逗号 , 分隔的位置组按顺序处理;分号 ; 可以把多个位置放入同一 location group。在多个活动 Profile 的场景下,两种写法会影响覆盖顺序。没有这类复杂需求时,建议不要混用多组配置位置。

3.5 Profile 文件的加载

Profile 让同一套应用代码能够使用不同环境参数,例如数据库、Redis、日志级别或服务端口。

3.5.1 推荐的文件组织

1
2
3
4
5
src/main/resources/
├── application.yml # 各环境通用配置
├── application-dev.yml # 开发环境差异
├── application-test.yml # 测试环境差异
└── application-prod.yml # 生产环境差异

application.yml:

1
2
3
4
5
6
7
8
9
10
spring:
application:
name: order-service

server:
port: 8080

app:
timeout: 5s
feature-enabled: false

application-dev.yml:

1
2
3
4
5
server:
port: 8081

app:
feature-enabled: true

application-prod.yml:

1
2
3
4
5
server:
port: 9090

app:
timeout: 10s

激活 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
java -jar app.jar --spring.profiles.active=dev,metrics

对于多个活动 Profile,同一位置组内 Profile 专属配置一般遵循后面的 Profile 覆盖前面的 Profile:例如 dev,metrics 时,适用的 application-metrics.yml 可覆盖 application-dev.yml 中的同名属性。

也可以使用 Profile Group,在基础配置中定义:

1
2
3
4
5
6
7
spring:
profiles:
group:
prod-full:
- prod
- observability
- mysql
1
java -jar app.jar --spring.profiles.active=prod-full

这样会启用 prod-full,以及组内的 prod、observability、mysql。

另有 spring.profiles.include 可以添加 Profile,而不只是替换 spring.profiles.active 的值:

1
2
3
4
spring:
profiles:
include:
- common

限制非常重要: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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration(proxyBeanMethods = false)
public class ClientConfiguration {

@Bean
@Profile("dev")
public PaymentClient mockPaymentClient() {
return new MockPaymentClient();
}

@Bean
@Profile("prod")
public PaymentClient realPaymentClient() {
return new RealPaymentClient();
}
}

以上示例的 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。

再补充三个限制条件:

  1. 同一位置、同一名称同时有 .properties 和 YAML 格式时,.properties 优先。
  2. 同一位置组中多个活动 Profile 遵循后者覆盖前者。 如果显式拆分了不同 location group,需要按照各组的处理顺序判断。
  3. spring.config.import 和自定义 spring.config.location 会改变简单的默认覆盖链。 不要把上面的四层表当成适用于所有自定义配置结构的绝对全序。

还要区分集合类型的覆盖规则。在 @ConfigurationProperties 绑定时:

1
2
3
4
5
# 低优先级来源
app:
servers:
- node-a
- node-b
1
2
3
4
# 高优先级来源
app:
servers:
- node-c

最终绑定到 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
2
3
4
5
应用命令行 --key=value
> JVM -Dkey=value
> 操作系统环境变量
> application.yml / application-prod.yml
> SpringApplication 默认属性

中间省略了部分特殊 PropertySource;因此它是日常部署速记,而不是上一张完整表的严格简化替代。

3.7.1 做一次真实的覆盖实验

在 application.yml 中写:

1
2
app:
name: from-yaml

然后执行:

1
2
3
APP_NAME=from-env \
java -Dapp.name=from-jvm -jar target/springboot-config-demo-1.0.0.jar \
--app.name=from-cli

最终 app.name=from-cli。如果去掉 --app.name=from-cli,就会得到 from-jvm;再去掉 -Dapp.name=from-jvm,才会得到 from-env。

3.7.2 环境变量命名与宽松绑定

推荐 YAML 中用小写短横线命名:

1
2
3
app:
service:
read-timeout: 3s

对应环境变量常写成:

1
export APP_SERVICE_READTIMEOUT=10s

映射规则是:点号 . 替换为下划线 _、短横线 - 删除、全部转为大写。因此 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 虽然优先级较高,但其中的 JSON null 通常被属性解析器视为“属性不存在”,不能指望 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
2
3
4
5
6
7
8
9
10
11
app:
name: ${APP_NAME:order-service}
timeout: ${APP_TIMEOUT:5s}
identifier: ${random.uuid}
endpoint: https://${API_HOST:api.example.com}/v1

spring:
datasource:
url: ${DB_URL:jdbc:mysql://localhost:3306/demo}
username: ${DB_USERNAME:demo}
password: ${DB_PASSWORD:}

解释如下:

  • ${APP_NAME:order-service}:优先读取 APP_NAME,否则使用默认值 order-service。
  • ${APP_TIMEOUT:5s}:默认值为 5s,绑定到 Duration 后可直接操作时间类型。
  • ${random.uuid}:获取随机 UUID,适合非持久化的临时标识;不要用它替代稳定业务 ID。
  • ${DB_PASSWORD:}:允许默认空串,但生产数据库等关键凭据应配合强制校验或启动失败策略,不要默默使用空密码。

属性之间也可以互相引用:

1
2
3
app:
host: api.example.com
url: https://${app.host}/v1

推荐在 Spring 占位符中使用规范的短横线形式(canonical kebab-case),例如 ${app.read-timeout},以便享受更一致的宽松查找规则。

Maven 占位符:

1
2
3
4
build:
version: "@project.version@"
artifact: "@project.artifactId@"
target: "@deploy.target@"

打包时 Maven 将其替换为 POM 中的值,例如 1.0.0、springboot-config-demo、local。当资源过滤未开启或者该文件未被过滤时,它们只会作为普通字符串留在最终 JAR 中,Spring Boot 不会自动解释 @project.version@。

不建议把 Maven 资源过滤的定界符改回 ${...}。因为 Maven 和 Spring 都使用这套语法时,可能在构建阶段误处理原本应该保留到运行时的 ${DB_HOST} 等配置。

4.2 使用 @Value 注入少量配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example.configdemo.web;

import java.time.Duration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class SimpleConfigReader {

private final String appName;
private final Duration timeout;
private final String buildVersion;

public SimpleConfigReader(
@Value("${app.name:unknown}") String appName,
@Value("${app.timeout:5s}") Duration timeout,
@Value("${build.version:dev}") String buildVersion) {
this.appName = appName;
this.timeout = timeout;
this.buildVersion = buildVersion;
}

public String describe() {
return appName + " / " + timeout + " / " + buildVersion;
}
}

@Value 适合小范围配置,不适合在几十个类中重复注入一大组配置项。复杂配置建议采用下一种方式。

4.3 @ConfigurationProperties:类型安全配置绑定

先准备 application.yml:

1
2
3
4
5
6
7
app:
name: ${APP_NAME:order-service}
endpoint: ${APP_ENDPOINT:https://api.example.com}
timeout: ${APP_TIMEOUT:5s}
servers:
- https://node-1.example.com
- https://node-2.example.com

在 Java 21 中可以直接使用 record:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example.configdemo.config;

import java.net.URI;
import java.time.Duration;
import java.util.List;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.validation.annotation.Validated;

@Validated
@ConfigurationProperties(prefix = "app")
public record AppProperties(
@NotBlank String name,
@NotNull URI endpoint,
@DefaultValue("5s") Duration timeout,
List<URI> servers
) {}

然后在启动类中启用配置扫描:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.example.configdemo;

import com.example.configdemo.config.AppProperties;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

@SpringBootApplication
@ConfigurationPropertiesScan
public class ConfigDemoApplication {

public static void main(String[] args) {
SpringApplication.run(ConfigDemoApplication.class, args);
}

@Bean
ApplicationRunner printConfig(AppProperties properties, Environment environment) {
return args -> {
System.out.println("Active profiles: "
+ String.join(",", environment.getActiveProfiles()));
System.out.println("App name: " + properties.name());
System.out.println("Endpoint: " + properties.endpoint());
System.out.println("Timeout: " + properties.timeout());
};
}
}

这样一来:

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

@Service
public class ConfigurationInspector {

private final Environment environment;

public ConfigurationInspector(Environment environment) {
this.environment = environment;
}

public String currentAppName() {
return environment.getProperty("app.name", "unknown");
}

public Integer currentPort() {
return environment.getProperty("server.port", Integer.class, 8080);
}

public boolean isProd() {
return environment.matchesProfiles("prod");
}
}

适合框架组件、诊断功能或按需读取;普通业务配置则更建议一次性绑定为配置对象,不要在业务代码中到处散落字符串 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
2
3
4
<properties>
<java.version>21</java.version>
<deploy.target>local</deploy.target>
</properties>

src/main/resources/application.yml:

1
2
3
4
5
6
7
8
9
10
11
12
spring:
application:
name: order-service

build:
version: "@project.version@"
artifact-id: "@project.artifactId@"
java-version: "@java.version@"
target: "@deploy.target@"

app:
name: ${APP_NAME:order-service}

在使用 spring-boot-starter-parent 默认资源过滤规则的情况下,运行:

1
mvn clean package -DskipTests

target/classes/application.yml 的对应内容会在构建时变成类似:

1
2
3
4
5
build:
version: "1.0.0"
artifact-id: "springboot-config-demo"
java-version: "21"
target: "local"

构建期填写软件版本、Git 提交信息或构建标识很合理;不要把生产数据库密码、访问令牌或其它长期敏感信息烘焙到 JAR 里。

5.3 在 POM 中使用 Maven Profile

可以给 POM 增加以下片段(放在 <project> 下,与 <build> 同级):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<profiles>
<profile>
<id>dev</id>
<properties>
<deploy.target>development</deploy.target>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<deploy.target>testing</deploy.target>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<deploy.target>production</deploy.target>
</properties>
</profile>
</profiles>

运行:

1
mvn clean package -Pprod

会把构建元数据 build.target 替换为 production,但这不会自动激活 Spring 的 prod Profile。如果你只是希望切换运行环境,正确做法通常是:

1
2
3
4
5
6
7
8
# 构建一次
mvn clean package

# 开发环境
java -jar target/springboot-config-demo-1.0.0.jar --spring.profiles.active=dev

# 生产环境(配合外部配置/密钥)
java -jar target/springboot-config-demo-1.0.0.jar --spring.profiles.active=prod

5.4 开发阶段通过 Maven 插件启动指定环境

1
2
3
4
5
6
7
8
9
10
# 运行 dev 环境
mvn spring-boot:run -Dspring-boot.run.profiles=dev

# 运行 test 环境
mvn spring-boot:run -Dspring-boot.run.profiles=test

# 向应用传递更多运行时参数
mvn spring-boot:run \
-Dspring-boot.run.profiles=dev \
-Dspring-boot.run.arguments="--server.port=8088 --app.name=from-maven-run"

这里的 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<build>
<resources>
<!-- 只过滤配置文件,避免将 ${...} 在构建时错误替换 -->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>application*.yml</include>
<include>application*.yaml</include>
<include>application*.properties</include>
</includes>
</resource>

<!-- 其他静态资源原样复制,避免破坏二进制文件 -->
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>application*.yml</exclude>
<exclude>application*.yaml</exclude>
<exclude>application*.properties</exclude>
</excludes>
</resource>
</resources>

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<delimiters>
<delimiter>@</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.5.16</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

上面的资源过滤配置仅针对 application* 文件且使用 @ 定界符;POM 属性仍使用 Maven 自身的 ${...} 语法。与企业已有 Parent 合并时,建议通过 mvn help:effective-pom 检查最终插件和 resources 配置,而不是重复覆盖已经存在的设置。

5.6 是否应该在 POM 中写 spring.profiles.active?

技术上可以利用 Maven 资源过滤,将:

1
2
3
spring:
profiles:
active: "@deploy.target@"

打包成一个带默认 Profile 的配置文件,但不推荐以此作为生产环境切换机制。因为:

  1. -Pprod 是构建时决定的,增加了构建产物的环境耦合。
  2. 同一份 JAR 无法自然地在测试和生产之间迁移(虽然运行时仍可用更高优先级属性覆盖)。
  3. deploy.target 之类的 Maven 构建名称不一定就是实际 Spring Profile 名称。
  4. 容器和平台部署通常更适合在运行时传入 SPRING_PROFILES_ACTIVE。

更合理的设计是:Maven 生成版本信息,Spring Boot 根据运行环境生成最终配置。

6. 一个从打包到部署的完整实践

6.1 基础配置与环境差异

application.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
application:
name: springboot-config-demo

server:
port: 8080

build:
version: "@project.version@"
target: "@deploy.target@"

app:
name: ${APP_NAME:order-service}
endpoint: ${APP_ENDPOINT:https://api.example.com}
timeout: ${APP_TIMEOUT:5s}
servers:
- https://node-1.example.com
- https://node-2.example.com

application-dev.yml:

1
2
3
4
5
6
server:
port: 8081

app:
name: order-service-dev
timeout: 2s

application-prod.yml:

1
2
3
4
5
6
server:
port: 9090

app:
name: order-service-prod
timeout: 10s

此时,前面定义的 ConfigDemoApplication、AppProperties、SimpleConfigReader 可以直接使用。默认 POM 不依赖任何数据库即可运行这个演示项目。

6.2 打包并验证资源过滤

1
2
3
4
mvn clean package

# 检查 Maven 是否已经替换构建期 @...@ 占位符
cat target/classes/application.yml

确保 build.version 已经变成 1.0.0,而 ${APP_NAME:order-service} 仍原样保留,等待 Spring Boot 在运行阶段处理。

6.3 同一份 JAR 分别启动两种环境

1
2
3
4
5
6
7
8
9
# 开发环境
java -jar target/springboot-config-demo-1.0.0.jar \
--spring.profiles.active=dev

# 生产环境(演示用,不涉及真实生产凭据)
APP_TIMEOUT=12s \
java -jar target/springboot-config-demo-1.0.0.jar \
--spring.profiles.active=prod \
--app.name=order-service-online

第二个命令中:

属性 默认配置 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
2
3
4
5
/opt/order-service/
├── app.jar
└── config/
├── application.yml
└── application-prod.yml

或者使用独立的配置目录:

1
2
3
/etc/order-service/
├── application.yml
└── application-prod.yml
1
2
3
java -jar /opt/order-service/app.jar \
--spring.profiles.active=prod \
--spring.config.additional-location=file:/etc/order-service/

这样可以在不修改 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
2
3
java -jar app.jar \
--spring.profiles.active=dev \
--logging.level.org.springframework.boot.context.config=TRACE

这有助于观察 Config Data 的解析与加载过程。日志可能非常详细,生产环境不要长期启用。

此外,Spring Boot Actuator 的 /actuator/env 和 /actuator/configprops 能帮助定位属性来源、绑定情况,但它们可能暴露系统配置结构;不要在公网或未授权环境开放这些诊断端点。必要时仅在受控环境短时启用,并启用适当认证、网络访问控制和敏感字段脱敏。

排查 Maven 构建则可使用:

1
2
3
4
5
6
7
8
# 当前激活的 Maven Profile
mvn help:active-profiles

# 查看最终生效的 POM
mvn help:effective-pom -Doutput=effective-pom.xml

# 查看 Maven 构建后的资源
cat target/classes/application.yml

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
2
3
4
5
最终配置值
= 构建阶段产生的资源内容
+ Config Data 按位置与 Profile 合并
+ Environment 中更高优先级 PropertySource 覆盖
+ 占位符解析及目标 Java 类型绑定

这不是数学加法,而是一个先构建、再发现、再覆盖、最后绑定的过程。真正理解它后,无论是本地开发、JAR 包部署、Docker、Kubernetes,还是 SaaS 多环境服务,排查配置问题时都会有一个稳定且可验证的思路。

参考资料

  1. Spring Boot 3.5 — Externalized Configuration:属性源完整优先级、配置搜索位置、Config Data、占位符、YAML 与类型安全绑定。
  2. Spring Boot 3.5 — Profiles:活动 Profile、默认 Profile、include、group 与 Profile 专属文件。
  3. Spring Boot 3.5 — Maven Plugin: Using the Plugin:Parent POM、资源过滤、@...@ 占位符以及 spring-boot.run.profiles。
  4. Apache Maven — Introduction to Build Profiles:Maven -P、POM Profile 的激活机制与构建配置覆盖。
  5. Spring Boot 3.5 — Properties and Configuration:多文档配置与属性配置实践。

Spring Boot 配置体系详解:YAML、加载顺序、Profile、属性注入与 Maven POM 整合
https://allendericdalexander.github.io/2026/10/15/java/spring/spring-boot-configuration-and-maven-integration/
作者
AtLuoFu
发布于
2026年10月15日
许可协议