文章目录
- 概要
- 一、案发现场:消失的"012"
-
- [1.1 解决方法](#1.1 解决方法)
- 二、深入分析
-
- [2.1 项目背景](#2.1 项目背景)
- [2.2 八进制陷阱](#2.2 八进制陷阱)
-
- [2.2.1 YAML 1.1的基本规范](#2.2.1 YAML 1.1的基本规范)
- [2.3 SnakeYAML 版本之谜](#2.3 SnakeYAML 版本之谜)
-
- [2.3.1 版本结论:](#2.3.1 版本结论:)
- 三、拓展
-
- [3.1 YAML 1.2 vs 1.1 对比](#3.1 YAML 1.2 vs 1.1 对比)
- 小结
- 说明
概要
在 Spring Boot 项目中,你是否遇到过 @Value 获取到的配置文件值与 application.yml 中配置的不一致 ?比如配置了 012 却读到了 10 ?或者 以 0 开头的数字配置被解析成了错误的值 ?
本文深入分析 Spring Boot 3.x 与 SnakeYAML 2.x 在处理 YAML 数字解析时的兼容性问题,揭示 YAML 八进制解析 的底层机制。
一、案发现场:消失的"012"
最近接手维护一个Spring Boot 项目,所有测试都通过了,临近上线的时候发现前端显示的组件版本号不一致的问题。配置文件中明明写的版本号是012(对应业务需求的第12个迭代版本),返回给前端的版本号却是10。
相关代码:
yaml
# yaml配置
app:
version: 012
java
// 代码中引用
@Value("${app.version}")
private String version;// 期望:"012",实际:"10"
1.1 解决方法
关键点在于 YAML 配置,String 类型需要加双引号
yaml
# yaml配置
app:
version: "012"
二、深入分析
2.1 项目背景
当前项目版本情况:
Spring Boot 3.5.7
SnakeYAML 2.4
2.2 八进制陷阱
首先,我们聊下造成 012 却读到了 10的根本原因
当配置里的值没有加双引号,
YAML默认是数字类型,且如果以0开头的,低版本自动按照八进制进行解析。
2.2.1 YAML 1.1的基本规范
YAML 1.1规范发布于2005年,它的核心理念是"数据序列化应该尽可能直观"。
1. 核心设计原则
隐式类型推断 :YAML解析器会根据内容自动推断数据类型
简洁性 :尽量减少引号和特殊符号的使用
可读性:配置看起来像自然语言
2. 具体的解析规则表

3. 类型检测流程图
输入标量值
↓
是否用引号括起来?
├─ 是 → 作为字符串处理
↓
否
↓
匹配类型模式(按顺序):
↓
- 是否为null值?(null, Null, NULL, ~, 空)
├─ 是 → 返回null
↓- 是否为布尔值?(yes/no, on/off, true/false等)
├─ 是 → 返回boolean
↓- 是否为整数?
├─ 是否以0开头且第二位不是x? → 八进制整数
├─ 是否以0x开头? → 十六进制整数
├─ 否则 → 十进制整数
↓- 是否为浮点数?(包含小数点或科学计数法)
├─ 是 → 返回float
↓- 是否为时间戳?(匹配ISO 8601格式)
├─ 是 → 返回Date
↓- 默认作为字符串处理
2.3 SnakeYAML 版本之谜
回顾项目背景:我们的Spring Boot 项目版本是 3.5.7 ,应该默认绑定的是 SnakeYAML 2.X 的版本。而SnakeYAML 2.X 的版本采用的应当是 YAML 1.2。不应该存在以0为开头的数字进行八进制转换,而是应该默认十进制。即使 012 转换也应该转换为12。
- YAML 1.1 (旧版) :规定以 0 开头的整数是八进制(例如 010 = 8)。Spring Boot 早期版本(依赖较旧的 SnakeYAML)通常遵循此标准,或者某些解析器为了兼容性保留了此行为。
- YAML 1.2 (新版) :修改了规范,规定八进制必须以 0o 开头(例如 0o10 = 8)。普通的 0 开头数字(如 010 )被视为十进制整数(10)。
我的项目pom树的snakeyaml版本:

为了验证到底是哪里出了问题,我写了个测试类:
java
import org.yaml.snakeyaml.Yaml;
import java.util.Map;
/**
* SnakeYAML 行为测试类.
* 用于验证当前 SnakeYAML 版本对八进制数字的解析行为.
*/
public class SnakeYamlTest {
public static void main(String[] args) {
String yamlContent = "root-code: 012";
System.out.println("---- Test 1: Default SnakeYAML 2.x Behavior ----");
// 默认配置
Yaml defaultYaml = new Yaml();
Map<String, Object> result = defaultYaml.load(yamlContent);
printResult(result);
}
private static void printResult(Map<String, Object> result) {
Object value = result.get("root-code");
System.out.println("Raw Value: " + value);
System.out.println("Type: " + (value == null ? "null" : value.getClass().getName()));
if (value instanceof Integer) {
int intVal = (Integer) value;
if (intVal == 10) {
System.out.println("Result: OCTAL (八进制) -> 012(8) = 10(10)");
} else if (intVal == 12) {
System.out.println("Result: DECIMAL (十进制) -> 012(10) = 12(10)");
}
} else {
System.out.println("Result: STRING or other");
}
}
}
结果如图 :

2.3.1 版本结论:
我本以为是
Spring Boot在捣鬼,结果发现SnakeYAML 2.4自己也是个"骑墙派"。默认情况下,它依然拥抱八进制。这意味着,单纯升级依赖并不能解决问题。①:你必须显式配置,必须手动设置
loaderOptions.setVersion(DumperOptions.Version.V1_2)才能获得真正的YAML 1.2行为;②:永远记得加引号
"";
三、拓展
3.1 YAML 1.2 vs 1.1 对比
YAML 1.2 是 YAML 语言的一个主要版本,于 2009 年发布。它旨在解决 YAML 1.1 中的一些设计缺陷,并与 JSON 保持更好的兼容性。
1. YAML 1.2 核心设计理念
JSON兼容性 :成为
JSON的超集简化规则 :减少隐式类型推断的复杂性
更好的可预测性 :让解析结果更明确
修复设计缺陷 :解决
1.1中的已知问题
2. 标量类型解析规则对比表

3. 具体语法示例对比
yaml
# ====================
# YAML 1.1 解析示例
# ====================
yaml_1_1:
numbers:
decimal: 123 # → 123
octal: 012 # → 10 (八进制)
phone: 013912345678 # 解析错误(含8,9)
booleans:
enabled: yes # → true
active: on # → true
ready: no # → false
specials:
time: 12:30:00 # → 基数60浮点数1.5
date: 2024-01-01 # → Date对象
null_value: ~ # → null
empty_string: "" # → null(有问题!)
# ====================
# YAML 1.2 解析示例
# ====================
yaml_1_2:
numbers:
decimal: 123 # → 123
octal: 012 # → 12(十进制!)
explicit_octal: 0o12 # → 10(需要显式)
phone: "013912345678" # 字符串,必须加引号
booleans:
enabled: true # → true
active: true # on 不再有效
ready: false # → false
specials:
time: "12:30:00" # → 字符串
date: "2024-01-01" # → 字符串
null_value: null # → null
empty_string: "" # → 空字符串(正确!)
4.解析器行为对比
YAML 1.1解析流程:输入标量 → 是否带引号? → 否 → 模式匹配:
- 空值检测 → 是 → null
- 布尔值检测 → 是 → boolean
- 整数检测 → 是否以0开头? → 是 → 八进制
- 浮点数检测 → 是 → float
- 时间戳检测 → 是 → Date
- 默认字符串
YAML 1.2解析流程:输入标量 → 是否带引号? → 否 → 模式匹配:
- 空值检测 → 是 → null
- 布尔值检测 → 是 → boolean
- 数字检测 → 十进制整数/浮点数
- 默认字符串
(八进制需要显式0o前缀)
小结
无论 SnakeYAML 是按 1.1 还是 1.2 解析,只要你不加引号:
- 它首先会被识别为 数字(
Number) 。- 既然是数字,前导零在数学意义上就是无用的,会被丢弃(除非是八进制标识)。
- 当你把它赋给
String时,得到的永远是"被扒光了"的纯数字字符串。
所以,想要保留012这种"前导零"格式,唯一的办法就是告诉解析器:"这不是个数字,这是个字符串"。 怎么告诉它? 加引号"012"。
说明
文中如有疑问欢迎讨论、指正,互相学习,感谢关注💡。