SprintBoot中使用@value注解获取不到配置值

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

0.碰到的问题

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

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

@Value("${spring.redis.used}")
复制代码

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

image.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截图

image.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,也不明白官方是啥意思

image.png

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

猜你喜欢

转载自juejin.im/post/7084635819691999239