Spring Boot Nacos配置无法覆盖@Value定义的默认值

那年烟雨落申城
• 阅读 227

缘起

新增了一个功能,使用了@Value("${xxx.aaa:b}")这种形式获取一个变量,默认值b是在配置中找不到对应的值时进行赋默认值,项目还集成了Nacos,在Nacos中配置了xxx.aaa=c,启动后发现获取的值是默认的,不是Nacos的,当把默认值去掉后,类似@Value("${xxx.aaa}")可以正确获取到Nacos的值

版本约定

Spring Cloud 版本 Dalston.SR4 Swagger2版本 2.6.0

分析

1. 是否是Nacos的问题?

明显不是,因为去掉默认值后,可以正确获取Nacos中配置的值,说明Nacos的值是可以获取到的,但不是第一优先级

2. 检查是否是值获取顺序问题

Spring Boot中有个值获取的规则

 #默认情况下,远程配置会优先于本地配置
spring.cloud.config.allowOverride
spring.cloud.config.overrideNone
spring.cloud.config.overrideSystemProperties

这几个值是默认的,配置里面没有配置它,所以还是远程优先于默认值

过程

@Value这个注解可以加在类属性上,也可以加在方法上,因为类属性的变化不好监控,于是给要赋值的变量写个setter,将注解加在setter

package org.springframework.beans.factory.annotation;
//省略import 
//可以看到Target可以是ElementType.METHOD
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
    String value();
}

于是,将

@Value("${mock.open:N}")
private String mockOpen;

改为

@Value("${mock.open:N}")
public void setMockOpen(String mockOpen) {
    this.mockOpen = mockOpen;
}

然后在this.mockOpen = mockOpen;这一句加上断点,Nacos对应的配置文件配置mock.open=Y。启动服务,等端点停留在this.mockOpen = mockOpen; 可以看到: Spring Boot Nacos配置无法覆盖@Value定义的默认值 我们顺着setMockOpen方法栈网上找找到图中的这个地方发现传入的参数是N,说明到这一步的时候,已经获取到N这个值了 这个方法签名是:

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement#inject

顺着上图中的arguments这个参数往上看,可以看到这个参数是内部计算出来的,那么计算逻辑就在当前方法了,方法逻辑如下:

protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
  if (!this.checkPropertySkipping(pvs)) {
       Method method = (Method)this.member;
       Object[] arguments;
       if (this.cached) {
       //可能是这里获取的
           arguments = this.resolveCachedArguments(beanName);
       } else {
           Class<?>[] paramTypes = method.getParameterTypes();
           //可能是这里获取的
           arguments = new Object[paramTypes.length];
           DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length];
           Set<String> autowiredBeans = new LinkedHashSet(paramTypes.length);
           TypeConverter typeConverter = AutowiredAnnotationBeanPostProcessor.this.beanFactory.getTypeConverter();

           for(int ixx = 0; ixx < arguments.length; ++ixx) {
               MethodParameter methodParam = new MethodParameter(method, ixx);
               DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
               currDesc.setContainingClass(bean.getClass());
               descriptors[ixx] = currDesc;

               try {
                   Object arg = AutowiredAnnotationBeanPostProcessor.this.beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
                   if (arg == null && !this.required) {
                       arguments = null;
                       break;
                   }

                   arguments[ixx] = arg;
               } catch (BeansException var17) {
                   throw new UnsatisfiedDependencyException((String)null, beanName, new InjectionPoint(methodParam), var17);
               }
           }

           synchronized(this) {
               if (!this.cached) {
                   if (arguments != null) {
                       this.cachedMethodArguments = new Object[paramTypes.length];

                       for(int i = 0; i < arguments.length; ++i) {
                           this.cachedMethodArguments[i] = descriptors[i];
                       }

                       AutowiredAnnotationBeanPostProcessor.this.registerDependentBeans(beanName, autowiredBeans);
                       if (autowiredBeans.size() == paramTypes.length) {
                           Iterator<String> it = autowiredBeans.iterator();

                           for(int ix = 0; ix < paramTypes.length; ++ix) {
                               String autowiredBeanName = (String)it.next();
                               if (AutowiredAnnotationBeanPostProcessor.this.beanFactory.containsBean(autowiredBeanName) && AutowiredAnnotationBeanPostProcessor.this.beanFactory.isTypeMatch(autowiredBeanName, paramTypes[ix])) {
                                   this.cachedMethodArguments[ix] = new ShortcutDependencyDescriptor(descriptors[ix], autowiredBeanName, paramTypes[ix]);
                               }
                           }
                       }
                   } else {
                       this.cachedMethodArguments = null;
                   }

                   this.cached = true;
               }
           }
       }

       if (arguments != null) {
           try {
               ReflectionUtils.makeAccessible(method);
               method.invoke(bean, arguments);
           } catch (InvocationTargetException var15) {
               throw var15.getTargetException();
           }
       }

   }
}

以上代码有两处可能获取的地方,是if或者else两个有一个分支会获取到,我们在方法进入后Method method = (Method)this.member;这一行代码加上断点,给断点设置条件beanName.equalsIgnoreCase("ESAttribute"),ESAttributesetMockOpen(String mockOpen)方法所在的类,然后重新启动服务。 断点生效后,单步调试发现进入了else逻辑: Spring Boot Nacos配置无法覆盖@Value定义的默认值 单步到下面 Spring Boot Nacos配置无法覆盖@Value定义的默认值 发现参数arg值是N,而下面有赋值语句arguments[ixx] = arg;,大概率就是这了, 丢弃到当前调用栈,重新进入,到图中的那一行单步进入方法, 然后 Spring Boot Nacos配置无法覆盖@Value定义的默认值 到这里(方法签名org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency)继续进入方法 然后 Spring Boot Nacos配置无法覆盖@Value定义的默认值方法签名org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency,上图中的方法再单步进入 Spring Boot Nacos配置无法覆盖@Value定义的默认值 这里do while循环的第一次循环就看到result计算的值是N了,查看下当前的Property解析器,发现 Spring Boot Nacos配置无法覆盖@Value定义的默认值 填充器居然是swaggerProperties,喵了个咪的,为啥不是Spring 自己的Property填充器,为啥是swagger的填充器,于是查看了一下this.embeddedValueResolvers这个 Spring Boot Nacos配置无法覆盖@Value定义的默认值 第二个才是Spring自己的Property填充器,那就到this.embeddedValueResolvers看看容器启动的时候放入顺序吧,顺便查下swagger的填充器是哪个配置装配进去的。 找到这里下个断点 Spring Boot Nacos配置无法覆盖@Value定义的默认值 方法签名

org.springframework.beans.factory.support.AbstractBeanFactory#addEmbeddedValueResolver

重启应用后,第一个进来的是Spring Boot Nacos配置无法覆盖@Value定义的默认值 确实是Spring自己的,然后让程序继续进行,进来的第二个是 Spring Boot Nacos配置无法覆盖@Value定义的默认值swaggerProperties,装配的类为 Spring Boot Nacos配置无法覆盖@Value定义的默认值 查找装配类的方法是在beanFactory下的beanDefinitionMap执行表达式((DefaultListableBeanFactory)((PropertyPlaceholderConfigurer)((PlaceholderResolvingStringValueResolver)valueResolver).this$0).beanFactory).beanDefinitionMap.get("swaggerProperties")可以看到swaggerPropertiesbeanDefinition信息,发现来自于springfox.documentation.swagger.configuration.SwaggerCommonConfiguration,这个类。 但是诡异的是,此时的this.embeddedValueResolvers居然是0 Spring Boot Nacos配置无法覆盖@Value定义的默认值 ,然后程序继续运行,这次来的就是Spring 的Property填充器了(我把第一次进来的和这次进来的Spring的填充器对比了一下,实例不是同一个,意味着第一次进来的那个被删除了,至于原因没有去深究,个人猜测第一个可能是Spring Boot放进来的,Spring Cloud因为支持类似Nacos这样的动态配置,扩展增强了Spring Boot的解析器,装配的时候吧Spring Boot的删除了,把自己增强后的放进来了),第三次进来的我这就不放截图了。 这时候,this.embeddedValueResolvers中有两个填充器,分别是swagger的和Spring的,swagger的在前,引发了文章开始那个问题。 那么我们来看下这个类springfox.documentation.swagger.configuration.SwaggerCommonConfiguration

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package springfox.documentation.swagger.configuration;

import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
//省略部分import
@Configuration
@ComponentScan(
    basePackages = {"springfox.documentation.swagger.schema", "springfox.documentation.swagger.readers", "springfox.documentation.swagger.web"}
)
public class SwaggerCommonConfiguration {
    public SwaggerCommonConfiguration() {
    }

    @Bean
    public static PropertyPlaceholderConfigurer swaggerProperties() {
        PropertyPlaceholderConfigurer propertiesPlaceholderConfigurer = new PropertyPlaceholderConfigurer();
        propertiesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
        return propertiesPlaceholderConfigurer;
    }
}

好家伙,直接定义了一个PropertyPlaceholderConfigurer,而且这个PropertyPlaceholderConfigurer还是Spring的,不知道为啥。

结论

分析至此,可以看到已经发现问题所在了,那么解决方案就呼之欲出了

  1. 如果不需要使用swagger了,直接去掉swagger配置即可
  2. 如果还需要使用swagger,升级swagger版本到2.7.0吧,2.7.0删掉了这个配置
点赞
收藏
评论区
推荐文章
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
springcloud eureka.instance
1.在springcloud中服务的 InstanceID默认值是:${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance\_id:${server.port}},也就是:主机名:应用名:应用端口。如图1
Stella981 Stella981
2年前
Nacos配置中心动态获取数组配置
有的时候我们需要动态获取一系列的配置项,假设我们在nacos配置中心的配置如下,nacos配置中心的其他设置请参考Nacos搭建流程(https://my.oschina.net/u/3768341/blog/3138297)skill:name:爆炸冲刺在SpringCloud代
Stella981 Stella981
2年前
Spring Cloud Alibaba系列(二)nacos作为服务配置中心
SpringCloudAlibaba系列(二)nacos作为服务配置中心Nacos提供用于存储配置和其他元数据的key/value存储,为分布式系统中的外部化配置提供服务器端和客户端支持。使用SpringCloudAlibabaNacosConfig,您可以在NacosServer集中管理你SpringCloud应用的外部
Wesley13 Wesley13
2年前
GO值类型与引用类型
值类型值类型包括基本数据类型,int,float,bool,string,以及数组和结构体(struct)。值类型变量声明后,不管是否已经赋值,编译器为其分配内存,此时该值存储于栈上。值类型的默认值:varaint//int类型默认值为0varbstring//string类型默认值为n
Wesley13 Wesley13
2年前
12、ES6形参默认值
当定义函数的时候,可以给参数设置默认值。调用的时候不传递参数值,就使用默认值。例子1:普通函数,不传递参数值。默认全是undefined。functionadd(a,b,c){console.log(a,b,c);}add();//不传递参数是,默认参数值全是undef
Wesley13 Wesley13
2年前
MySQL 5.7 新增参数
这些数据是通过” showglobalvariables;” 获得,可能不是所有的新增参数。我的MySQL版本是5.7.24。参数默认值binlog\_group\_commit\_sync\_delay0binlog\_group\_commit\_sync\_no\_delay\_count0binlog\_tr
Wesley13 Wesley13
2年前
Java 构造方法
构造方法什么是构造方法:构造方法就是与类同名的那个方法且没有返回值。就是一个方法。有什么作用:就是初始化对象的成员变量,无参的构造方法,系统自动初始化。有参则根据你的要求初始化不同的类型,默认值如下:实例成员变量默认值:boolean:falsebyte:0short:0char:int:
Wesley13 Wesley13
2年前
Java构造器的实质作用
Java构造器的实质作用构造器的本质作用就是为对象初始化,即为实例变量初始化,赋初值;而不是创建对象,创建对象时通过new关键字来完成的,当使用new关键字时就会为该对象在堆内存中开辟一块内存,只等构造器来初始化这块内存,为实例变量赋初始值。在未赋初始值之前是默认值。看代码中的构造器和编译后构造器是不一样的,编译后的构造器包含了更多的内容。
小万哥 小万哥
2星期前
C++ 默认参数与引用传递:语法、用法及示例
C默认参数默认参数概述在C中,函数参数可以拥有默认值。这意味着,在调用函数时,如果省略了某个参数,那么将使用为该参数指定的默认值。设置默认参数默认参数值使用等号符号进行设置,位于参数声明的类型之后。例如:cvoidmyFunction(stri