nacos中配了一个数字,springboot取回来怎么变了

背景

对于java开发人员来说,nacos想必不陌生了,我们这边是拿来做配置中心为主。我这边的习惯用法是,在bootstrap.yml中配置nacos相关的配置、profile:

然后呢,可以看到,nacos是支持启用或者不启用的,如果为true,就会使用nacos上的配置;我本地开发的时候,随时会把配置改来改去,我一般设置为false,不启用,此时就会使用本地的application-dev.yml中的配置。

开发得差不多了,再切换成使用nacos中配置,测一下就差不多了。

复制代码
{
  "name": "spring.cloud.nacos.config.enabled",
  "type": "java.lang.Boolean",
  "description": "enable nacos config or not.",
  "defaultValue": true
},

结果呢,最近遇到个小问题。如下所示,有个appCode,这个配置项,是一个应用的唯一编码,开发环境配置如下,不论使用启用nacos,这块在程序中获取的配置都是正确的。

对应的spring中接收配置的代码如下:

然后,转到测试环境后,换成了如下配置,也就是code变了,结果程序中取出来,就成了:

appCode怎么是266啥的,我当时和测试同事仔细检查了下,确认没配置错误。

那,这是咋回事呢

定位过程

排除nacos问题

由于是测试环境,网络都是通的,我直接在本地ide连测试环境,复现了下问题,抓了个本地和nacos之间的包:

看了下一点问题没有。接下来就是看代码怎么处理的了。

propertysouece

先弄个field断点,看看写入时候的栈。其实如果对nacos熟悉的话,也可以正向排查,从nacos获取到配置后的处理过程开始看,我这里就先按照反向流程来。

然后就看到断点停住了,进来的值确实不对:

把栈往上翻了翻,发现是在创建bean的过程中调用这块方法的。我们知道,一个bean的创建,一般会有:调用构造函数--》注入field的值(比如那些设置了@autowired的field、或者是我这这种注解了@ConfigurationProperties(prefix = "app")的,等等)--》调用init方法或afterpropertiesSet方法等。

此时,我的栈就处于第二步,此时bean已经通过构造函数弄出来了,正在注入ConfigurationProperties相关属性的值。

翻到上面的caller栈,发现下面这个地方,获取到的bound字段的值已经是2开头,是错的了,那看来就是这个地方,取到了错误的值,那就这里打个断点再来。

这次从上面断点进来后,发现进入了一个findProperty的方法,如下,主要就是根据app.app-list-need-query-todo-num[0].app-code这么个属性,要获取到对应的value,这也正常,要先获取到值,才能设置到bena里。

可以看到,获取property主要是从propertySource中获取。这个property是啥类型呢,

org.springframework.boot.context.properties.source.SpringIterableConfigurationPropertySource,里面有另一个propertySource字段,这个字段的类型是:org.springframework.cloud.bootstrap.config.BootstrapPropertySource,而下图可看到,BootstrapPropertySource中的delegate字段,才指向真正的propertySource,即NacosPropertySource

这里我们跟进去后,发现,property的格式不太一致,nacos中的存储都是驼峰格式,而这里获取配置的property是中划线格式,所以这次是获取不到的。

不过,spring做了兼容,会对property进行转换,变成驼峰格式再来找一次:

这次能找到了,但找到的是错误的,所以,我们还得看看nacosPropertySource中的值,为啥是错的。

nacosPropertySource中的值,存放在source这个字段中,类型是LinkedHashMap,我们就看看这个map是什么时候赋值的。

我们发现,这个source应该是构造函数时候,设置进来的,如下:

找了下调用这个构造函数的地方,如下,这里就会去先获取nacos中的配置,然后调用构造函数,我们看了下,发现下面红框的propertySource中已经是错误的了,得再打个断点跟进去

loadNacosData

下面这个方法中,先就是去nacos服务端获取到配置,然后再进行解析,可以看到,从服务端取到的是对的,那就是解析的问题,继续跟进去:

由于nacos中配置支持多种格式,如下图所示,有多种解析器,我们这边是yaml,类型是:org.springframework.boot.env.YamlPropertySourceLoader,这个loader是spring-boot自带的:

这个org.springframework.boot.env.YamlPropertySourceLoader实际自己并不能独立完成yaml格式文件的解析,而是依赖另一个jar包:

xml 复制代码
    <dependency>
      <groupId>org.yaml</groupId>
      <artifactId>snakeyaml</artifactId>
      <version>1.30</version>
      <scope>compile</scope>
    </dependency>

yaml

yaml这个具体的解析逻辑就比较复杂了,可以使用条件断点,不然很难debug。

下面可以看到,如果这个字符串,是0开头,就认为是8进制了,且会去掉开头的0,如:01011003,由于首位为0,认为是8进制,然后去掉符号位,值为:1011003

然后把1011003转成8进制数字,变成了266755

为啥开发环境时的010190那个code没转成8进制了,可能是转换失败了(但我发现改成010190后,没走上面那段逻辑,里面yaml解析挺复杂,就没继续跟了)。

解决方式

由于是进制转换的问题,我这里的解决方式是,直接在nacos用字符串来配置,避免这些数字转换问题。