The configuration value cannot be obtained using the @value annotation in SprintBoot

! ! ! The following content is original by the author and first published on the Nuggets platform. Without the consent and permission of the original author, no one or any organization may reproduce it in any form. It is not easy to be original. If it provides a little help to your problem, I hope to get your support.

0. Problems encountered

Record a recent problem encountered in the company's project development: the @value annotation is read-only and the default value cannot be read in the configuration file. Record the mental journey of the solution for your reference.

Because development needs to add a new parameter spring.redis.used in application.properties. In actual use, it is introduced as follows:

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

This is very common, but it brings a problem:
once the code is written and submitted, the local configuration file is not submitted. Many small partners in the company do not know that the startup configuration file has been changed, resulting in an error directly when the project starts:

image.pngAlthough everyone can update this startup parameter, it is somewhat not "perfect". Then I tried to find a better solution through Baidu, so I found the following method. In this way, the default value after the colon will be taken if it is not present in the startup configuration file.

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

After testing, it can achieve the purpose: without configuration parameters, the project can be started normally, and the default value is obtained. At this point, the problem seems to be solved.

But repeated testing found another new problem:

The parameters in the configuration items cannot be obtained. Even if they are configured, they are still read out as default values.

1. Problem analysis

1.1 Find the source of the problem

After searching layer by layer, I finally found the method to resolve @Value in the spring source code: resolveEmbeddedValue in AbstractBeanFactory

@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;
}
复制代码

Analyze this code a little bit. It is to process all @value annotations through the value parser configured in the loop project to obtain the parsed value. Then I found the following sentence is the problem:

result = resolver.resolveStringValue(result);

Each parser takes the result to process, and puts the processing result back into the result after processing. If the parser does not read it, but reads the default value, all subsequent parsers have been parsing and processing the default value, so our project cannot read the real value regardless of whether this parameter is configured or not. .

当时就很不解了,公司项目并没有配置过额外的解析器啊,按理就只有一个 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 里面去看了最新的代码依旧如此。

Then I went to issues and searched with the following keywords:

is:issue is:open resolveEmbeddedValue

Sure enough, 2 buddies asked almost the same question as us. Here is the link: Interested friends can come and have a look

# 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

They were all proposed in 2020. After reading the label of the state: waiting-for-triage, I don’t understand what the official means.

image.png

Set up a flag, and when you think about it in the future, go to see if the official will accept the suggestions in the issue, and re-modify this processing code.

Guess you like

Origin juejin.im/post/7084635819691999239