Spring Boot 配置文件从 properties 迁移到 yaml:步骤与常见坑
把 Spring Boot 的 application.properties 迁移到 application.yml,核心就是三件事:点号分隔的层级改成缩进,a[0]=x 改成 - x,多环境文件合并成用 --- 分隔的多文档。真正容易出问题的不是语法转换,而是迁移之后的行为差异:properties 优先级更高、YAML 会改变值的类型、@PropertySource 读不了 yml、含特殊字符的值要加引号。下面按步骤走一遍,每个坑都附了我在 Spring Boot 3.5.7 上的实测结果。
一、迁移前先确认三件事
- Spring Boot 版本。多环境写法在 2.4 前后不一样:2.4 之前用
spring.profiles给文档指定环境,2.4 起改为spring.config.activate.on-profile。你的版本决定了第三步怎么写。 - 有没有代码在直接读配置文件。全局搜索
@PropertySource、Properties.load、ClassPathResource("application。通过 Spring 环境读取(@Value、@ConfigurationProperties、Environment)的不受影响,直接读文件的会出问题。 - 备份并确保能回滚。最简单的做法是先提交一次 git,迁移作为独立提交,出问题
git revert即可。
二、第一步:用工具转换,再人工整理
假设现有的 application.properties 是这样的:
server.port=8081
server.servlet.context-path=/api
spring.application.name=demo-service
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=Abc@123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
logging.level.root=info
logging.level.com.example.demo=debug
app.cors.allowed-origins[0]=https://a.example.com
app.cors.allowed-origins[1]=https://b.example.com
app.feature.enabled=true
app.welcome=Hello World
打开 ToYaml.com 的 Properties ⇄ YAML 转换器,把内容粘贴到左边,点 to yaml。转换结果在结构上是正确的,但有两点要手动处理:一是转换器输出的键按字母顺序排列(app 在最前,spring 在后面),和你原来的阅读习惯不同;二是 logging.level.com.example.demo 会被拆成 com → example → demo 三层嵌套,写起来啰嗦。整理之后的版本:
server:
port: 8081
servlet:
context-path: /api
spring:
application:
name: demo-service
datasource:
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: Abc@123
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
logging:
level:
root: info
com.example.demo: debug
app:
cors:
allowed-origins:
- https://a.example.com
- https://b.example.com
feature:
enabled: true
welcome: Hello World
我用 Spring Boot 的加载器把这两份文件分别展开,15 个属性的键和值逐项一致。注意 JDBC 地址里有 :、?、&,但它们后面都不跟空格,所以 YAML 里不用加引号;只有出现 ": "(冒号加空格)或 " #"(空格加井号)时才必须加。
三、第二步:核对写法对应关系
| Properties | YAML | 说明 |
|---|---|---|
a.b.c=1 | a: → b: → c: 1 | 每个点号对应一层缩进(2 个空格) |
list[0]=x | list: 下 - x | 下标变成 -,顺序即下标 |
list[0].name=a | - name: a | 对象列表,同一个元素的属性要缩进对齐 |
hosts=a,b,c | hosts: a,b,c 或写成列表 | 绑定到 List<String> 时两种都行 |
key=(空值) | key: 或 key: '' | Spring Boot 中读到的都是空字符串 |
# 注释 | # 注释 | 写法相同 |
对象列表是最容易写错缩进的地方。看一个对照:
app.servers[0].name=a
app.servers[0].port=1
app.servers[1].name=b
app.servers[1].port=2
app:
servers:
- name: a
port: 1
- name: b
port: 2
要点:- name: a 里的 name 和下一行的 port 必须左对齐(- 后面的空格也算缩进)。我试过把 - a 下一行的 - b 多缩进一个空格,结果两个元素被悄悄合并成了一个字符串 "a - b",没有任何报错,所以列表写完要数一数元素个数。
四、第三步:多环境配置
迁移前常见的结构是 application.properties 加 application-dev.properties、application-prod.properties。迁移到 YAML 有两种做法,都可以用。
做法一:保持多文件,只改后缀
application-dev.yml 和 application-prod.yml 与 application.yml 并列。我用 application.yml 写 app.name: base、application-dev.yml 写 app.name: dev-file,以 --spring.profiles.active=dev 启动,读到的是 dev-file。这种方式改动最小,团队成员最容易接受。
做法二:一个文件多文档
用 --- 把不同环境写在同一个 application.yml 里。Spring Boot 2.4 及以上这样写:
app:
name: base
note: shared
---
spring:
config:
activate:
on-profile: dev
app:
name: dev-name
---
spring:
config:
activate:
on-profile: prod
app:
name: prod-name
实测结果:不指定环境时 app.name 是 base;--spring.profiles.active=dev 时是 dev-name,app.note 仍然继承自第一段的 shared;指定 prod 时是 prod-name。第一个文档(没有 on-profile)是所有环境共享的默认值。
老写法在新版本里会直接启动失败。如果你照着老教程写 spring.profiles: dev,在 Spring Boot 2.4.2 上我实测只会打一行 WARN,并且仍按老逻辑生效;但在 Spring Boot 3.5.7 上,应用启动时直接抛出异常:
org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property 'spring.profiles' imported from location 'file [application.yml]' is invalid and should be replaced with 'spring.config.activate.on-profile' [origin: URL [file:application.yml] - 5:13]
把 spring.profiles 全部替换成 spring.config.activate.on-profile 即可。
如果某些文件暂时保留 properties
properties 文件也能做多文档:用单独一行 #--- 分隔。我在 Spring Boot 3.5.7 和 2.4.2 上都验证过下面的写法,激活 dev 时 app.name 是 dev-name,不激活时是 base:
app.name=base
#---
spring.config.activate.on-profile=dev
app.name=dev-name
用在线转换器把多文档 YAML 转成 properties 时要留意:转换器会把所有文档合并成一个文件,app.name=base 和 app.name=dev-name 依次出现,后一个会覆盖前一个。这种情况请手动拆开,或者保留 yml 格式。
五、第四步:删除旧文件,别让两份配置共存
迁移完成后务必把 application.properties 删掉(或至少移出 classpath)。原因前面提过:同目录下 properties 优先于 yml,旧文件里的值会悄悄覆盖新文件,表现为"改了 yml 不生效"。我的复现:properties 里 app.name=from-properties,yml 里 app.name: from-yml,最终读到 from-properties。同时存在时,没有冲突的键还会合并,这会掩盖遗漏——你以为迁完了,其实有些配置还在读旧文件。
六、第五步:验证迁移结果
最可靠的办法是"转回去对比":把迁移后的 yml 再用转换器的 to prop 转成 properties,然后和原文件排序后比较。保存为 back.properties 之后:
diff <(grep -v '^#' application.properties | sort) <(grep -v '^#' back.properties | sort) && echo SAME
我用上面第二节的 15 行样例做了一遍,输出 SAME,说明键和值都没丢。注意转换器会丢弃注释和空行,所以比较时先过滤掉注释。另外需要确认的是类型:下一节列的几类值,转换器不一定替你处理。
七、迁移后的常见坑
1. 值被当成别的类型
YAML 会把 yes、no、on、off 解析为布尔值,把以 0 开头的数字当成八进制(001234 读成 668),把 12:30 当成六十进制整数(读成 750),3.10 读成浮点数 3.1。这些在 properties 里都只是字符串。解决办法是给这类值加引号。详细清单见 YAML 常见语法错误排查。
2. 特殊字符开头的值
以 @、*、%、! 或反引号开头的值,YAML 里必须加引号。最常见的是 Maven 资源过滤:spring-boot-starter-parent 默认用 @ 作为占位符分隔符,所以 properties 里的 app.version=@project.version@ 迁移到 YAML 后必须写成 version: '@project.version@'。不加引号的话,Spring Boot 报错:
found character '@' that cannot start any token. (Do not use @ for indentation)
in 'reader', line 2, column 9:
mail: @company.com
^
3. @PropertySource 读不了 yml
这是迁移时最隐蔽的坑。@PropertySource("classpath:extra.yml") 不会报错,但它按 properties 语法解析 yml:缩进的子键被当成顶层键。我用下面的 yml 实测:
app:
extra: hello
server:
port: 9090
用 @Value("${app.extra:NOT_FOUND}") 读取得到的是 NOT_FOUND,而不带前缀的 @Value("${extra:NOT_FOUND}") 反而读到了 hello。解决办法是自己提供一个 PropertySourceFactory:
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.IOException;
import java.util.Properties;
public class YamlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
Properties props = factory.getObject();
return new PropertiesPropertySource(
name != null ? name : resource.getResource().getFilename(), props);
}
}
使用时写 @PropertySource(value = "classpath:extra.yml", factory = YamlPropertySourceFactory.class)。我编译运行过这个类,app.extra 能正确读到 hello。如果只是想加载额外的配置文件,更推荐 Spring Boot 2.4+ 的 spring.config.import=classpath:extra.yml,它走的是和 application.yml 相同的加载器。
4. 键名写法与绑定
@ConfigurationProperties 支持宽松绑定。我在类里声明 maxPoolSize 字段,分别在配置文件里写 app.max-pool-size、app.maxPoolSize、app.max_pool_size,三种写法都绑定成功;用环境变量 APP_MAXPOOLSIZE 或 APP_MAX_POOL_SIZE 也能覆盖,命令行 --app.max-pool-size=23 同样有效。但 @Value 就严格得多:配置里写 app.max-pool-size 时,@Value("${app.max-pool-size}") 能取到,而 ${app.maxPoolSize} 和 ${app.max_pool_size} 都取不到。所以文件里统一用小写加连字符(kebab-case),既是官方推荐,也能避开 @Value 的坑。
5. 同名列表整体替换
多处配置了同一个列表时,不会逐项合并,而是优先级高的整体替换。实测 yml 里 app.hosts 有 a、b、c 三项,properties 里只写 app.hosts[0]=x,最终结果是只有 x。迁移期如果新旧文件并存,这类问题很难发现。
6. 反斜杠与中文
properties 里 C:\temp\new 会被解析成 Tab 加换行,必须写成 C:\\temp\\new;YAML 的普通写法里反斜杠不转义,C:\temp\new 原样保留(但在双引号字符串里反斜杠会被当作转义,我试过 "C:\temp\x" 会直接报 expected escape sequence of 2 hexadecimal numbers,用单引号或不加引号即可)。中文方面,迁移到 YAML 后可以直接写汉字,不再需要 \uXXXX 转义——Spring Boot 读 properties 按 ISO-8859-1,而读 YAML 按 UTF-8。
八、迁移清单
- 确认 Spring Boot 版本,决定多环境写法。
- 用转换器生成 yml,按阅读习惯调整顺序。
- 检查数字样字符串(版本号、邮编、工号)、
yes/no/on/off、@开头的值,必要时加引号。 - 多环境配置改成多文件或多文档,把
spring.profiles换成spring.config.activate.on-profile。 - 检查
@PropertySource,yml 文件要么换spring.config.import,要么提供自定义工厂。 - 删除旧的 properties,转换回 properties 做排序 diff。
- 在测试环境启动一次,确认各环境的关键配置值(端口、数据源、开关)符合预期。
想进一步了解两种格式的差别可以看 YAML 和 Properties 有什么区别;转换前后如果想看看 YAML 对应的 JSON 结构,用 YAML ⇄ JSON 工具。
读完就动手试试:粘贴你的配置,一键转换并检查结果。