文章目录
- 一、现象
- 二、原因排查
- 三、怎么办
一、现象
某次在配置中心Nacos上配置了一个字符串,采用YAML格式,如下:
id: 114:1
代码中采用的是@Value注解的形式获取配置:
@Value("${id}")
代码中预期获取的是字符串:“114:1”,但是日志打印出来的却是 6841。
二、原因排查
一开始,我以为是Nacos的问题,同事提醒说直接在application.yml文件中这么配置,解析出来的也是 6841,所以可以确定是SpringBoot解析的时候出的问题。我先尝试把114:1,加上双引号,
id: "114:1"
程序就可以正常获取到字符串"114:1"。
于是开始排查SpringBoot项目启动解析yml文件的过程,一步步断点,调试,发现SpringBoot项目是采用snakeyaml解析yml配置文件的。
SprintBoot版本:2.5.2,里面以来的 org.yaml.snakeyaml 版本:1.28
线程栈如下(从下往上):
我在application.yml中配置如下(id-2 作为 id 的比对):
server:port: 8080
id: 114:1
id-2: "114:1"
在 org.yaml.snakeyaml.constructor.BaseConstructor 类的 constructObjectNoCheck(Node node) 方法,可以看到:
snakeyaml解析 id 对应的 valueNode 类型竟然是 int 类型,而作为 id-2 对应的是我们想要的字符串 str 类型。接着往下,SafeConstructor 类中的内部类 ConstructYamlInt 解析 int 类型的值:
public class ConstructYamlInt extends AbstractConstruct {@Overridepublic Object construct(Node node) {String value = constructScalar((ScalarNode) node).toString().replaceAll("_", "");int sign = +1;char first = value.charAt(0);if (first == '-') {sign = -1;value = value.substring(1);} else if (first == '+') {value = value.substring(1);}int base = 10;if ("0".equals(value)) {return Integer.valueOf(0);} else if (value.startsWith("0b")) {value = value.substring(2);base = 2;// 二进制} else if (value.startsWith("0x")) {value = value.substring(2);base = 16;//十六进制} else if (value.startsWith("0")) {value = value.substring(1);base = 8;//八进制} else if (value.indexOf(':') != -1) {//按秒解析冒号(:),这就是问题原因。//配置的114:1被解析为6841String[] digits = value.split(":");int bes = 1;int val = 0;for (int i = 0, j = digits.length; i < j; i++) {val += Long.parseLong(digits[j - i - 1]) * bes;bes *= 60;}return createNumber(sign, String.valueOf(val), 10);} else {return createNumber(sign, value, 10);}return createNumber(sign, value, base);}}
这里判断是否有“:”,有的话会把数据按照时间单位秒数(60进制)进行解析返回。这里想不通为什么有这个秒数转换的实现。另外,还要注意的是,这里按“0x”开头的字符串如果不加引号,则按十六进制整数规则解析;“0”开头的字符串不加引号,则按八进制整数规则解析;“0b”开头的字符串不加引号,则按二进制整数规则解析;注意,以上前提是字符串能够按照对应的进制解析成功。
# 0x21 不加引号,解析为33
id: 0b11
# 021 不加引号,解析为17
id-2: 021
# 0b11 不加引号,解析为3
id-3: 0b11
# 0x2p2 不加引号,但无法按十六进制正常解析,按字符串解析的,解析为0x2p2
id: 0x2p2
三、怎么办
遇到这样的带冒号的字符串,就老老实实把引号加上就可以了。