解决Sprint Boot 中使用 @value 注解获取不到配置值

 2023-01-23
原文作者:双心 原文地址:https://juejin.cn/post/7084635819691999239

!!!以下内容为作者原创,首发于掘金平台。未经原作者同意与许可,任何人、任何组织不得以任何形式转载。原创不易,如果对您的问题提供了些许帮助,希望得到您的点赞支持。

0.碰到的问题

记录下最近公司项目开发中碰到的一个问题:@value 注解只读默认值,读取不到配置文件中的值,记录一下解决的心路历程,供大家参考。

因为开发需要在application.properties的中新加了一个参数spring.redis.used。实际使用中通过下面这样来引入:

    @Value("${spring.redis.used}")

这都很常见,但是带来一个问题:
代码写完一提交,并没有提交本地配置文件,很多公司小伙伴不知道改动了启动配置文件,造成项目启动就直接报错了:

202301012031132081.png 虽然可以让大家都更新上这个启动参数,但是多少有点不“完美”。遂通过百度想找更好点的解决方法,于是找到下面方法。通过这种方式,如果启动配置文件中没有,就会取冒号后面的默认值。

    @Value("${spring.redis.used:false}")

经过测试能达到目的:没有配置参数,也能正常启动项目,而且是取到默认值。问题到这一步,似乎解决了。

但是反复测试发现另一个新的问题:

取不到配置项中的参数了,即使配置了,读出来依旧是默认值

1.问题分析

1.1找到出问题出处

经过层层寻找,终于找到 spring 源码中解析@Value的方法: AbstractBeanFactory中的resolveEmbeddedValue

    @Override
    @Nullable
    public String resolveEmbeddedValue(@Nullable String value) {
       if (value == null) {
          return null;
       }
       String result = value;
       for (StringValueResolver resolver : this.embeddedValueResolvers) {
          result = resolver.resolveStringValue(result);
          if (result == null) {
             return null;
          }
       }
       return result;
    }

稍微分析一下这段代码。就是通过循环项目中配置的value解析器,处理所有@value 注解,获取到解析后的值。然后发现下面这句就是问题所在:

result = resolver.resolveStringValue(result);

每个解析器将 result 拿来处理,处理完后将处理结果又放回到result。如果这个解析器没有读取出来,而是读了默认值,那后面的所有解析器其实一直对这个默认值在解析处理,所以引发我们的项目不管有没有配置这个参数都读不到真实的值。

当时就很不解了,公司项目并没有配置过额外的解析器啊,按理就只有一个 springboot 自动的配置。所以就调试到这块,拿到真实的 embeddedValueResolvers 值看个究竟。

1.2为什么会出现多个解析器

下面放debug截图

202301012031163502.png

如果是用过 ureport 报表的同学应该能看明白了,这两个解析器都是因为我们项目中引入了一个开源报表工具 Ureport,而这个开源报表里面自动配置了这两个解析器

对这个开源报表的配置就不进入分析了,总之就是这个工具引入导致的。

那这时下一个疑问就出来了,为什么这两个Ureport 解析器在前面,而 springboot 默认的解析器位于最后呢?

1.3 Resolver 的加载顺序

下面是这3个解析器的 Order属性:

第1个解析器:Order 100
第2个解析器:Order 2147483647
第3个解析器(springboot自带):Order 2147483647

下面两个order 值应该是没有配置过 order 属性而自带的,第1个order 应该是在初始化时有配置文件指定的,于是经过一阵搜索找到下面这段 Ureport 的配置代码

    package com.bstek.ureport;
    
    import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
    
    public class UReportPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
        public UReportPropertyPlaceholderConfigurer() {
            this.setIgnoreUnresolvablePlaceholders(true);
            this.setOrder(100);
        }
    }

原来如此,那我们的解决方法就和它一样,把springboot 默认的解析器 order 顺序配置到 Ureport 前就好了

1.4 重新配置 spring resolver 顺序

在项目中加入一段自定义配置:

    @Configuration
    public class PropertySourcesPlaceholderConfigurer extends org.springframework.context.support.PropertySourcesPlaceholderConfigurer {
       PropertySourcesPlaceholderConfigurer(){
          setIgnoreUnresolvablePlaceholders(true);
          setOrder(99);
       }
    }

测试下来正常解决该问题。

2.事后思考

虽然我们的问题得到解决,但是始终觉得 spring 在解析那块的处理似乎有问题。这样如果有多个配置文件,默认解析器没有读取到配置文件,是不是也会造成后面自定义的解析器也得不到真实值?

我也怀疑是不是我们引入的 spring 版本过旧了,而新的版本是不是已经修复此问题?
于是到 spring 的github 里面去看了最新的代码依旧如此。

随后又去issues 中用下面关键词搜了一下:

is:issue is:open resolveEmbeddedValue

果然,出现2个哥们提出和我们几乎一样的问题,下面是链接:感兴趣的小伙伴们可以串过去看看

# When default value is configured for property placeholder in @Value, subsequent embedded value resolvers are asked to resolve the default value instead of the original value #26328

# Allow next resolver to resolve when current resolver return null #26247

都是2020年提出来的了,看了下状态的标签是:waiting-for-triage,也不明白官方是啥意思

202301012031176673.png

立个 flag ,以后想起来时再去看看官方会不会接收 issue中的建议,重新修改这段处理代码。