SpringBoot应用Yaml 解析冒号(:)字符串踩坑排查

一、现象【What】

某次在配置中心Nacos上配置了一个字符串,采用YAML格式,如下:

id: 114:1

代码中采用的是@Value注解的形式获取配置:

@Value("${id}")

代码中预期获取的是字符串:"114:1",但是日志打印出来的却是 6841。

二、原因排查【Why】

一开始,我以为是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 {
        @Override
        public 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被解析为6841
                String[] 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

三、怎么办(How)

遇到这样的带冒号的字符串,就老老实实把引号加上就可以了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值