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

缘起

新增了一个功能,使用了@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中有个值获取的规则

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

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

过程

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

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

于是,将

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

改为

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

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

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

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

1protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { 2 if (!this.checkPropertySkipping(pvs)) { 3 Method method = (Method)this.member; 4 Object[] arguments; 5 if (this.cached) { 6 //可能是这里获取的 7 arguments = this.resolveCachedArguments(beanName); 8 } else { 9 Class<?>[] paramTypes = method.getParameterTypes(); 10 //可能是这里获取的 11 arguments = new Object[paramTypes.length]; 12 DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length]; 13 Set<String> autowiredBeans = new LinkedHashSet(paramTypes.length); 14 TypeConverter typeConverter = AutowiredAnnotationBeanPostProcessor.this.beanFactory.getTypeConverter(); 15 16 for(int ixx = 0; ixx < arguments.length; ++ixx) { 17 MethodParameter methodParam = new MethodParameter(method, ixx); 18 DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required); 19 currDesc.setContainingClass(bean.getClass()); 20 descriptors[ixx] = currDesc; 21 22 try { 23 Object arg = AutowiredAnnotationBeanPostProcessor.this.beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter); 24 if (arg == null && !this.required) { 25 arguments = null; 26 break; 27 } 28 29 arguments[ixx] = arg; 30 } catch (BeansException var17) { 31 throw new UnsatisfiedDependencyException((String)null, beanName, new InjectionPoint(methodParam), var17); 32 } 33 } 34 35 synchronized(this) { 36 if (!this.cached) { 37 if (arguments != null) { 38 this.cachedMethodArguments = new Object[paramTypes.length]; 39 40 for(int i = 0; i < arguments.length; ++i) { 41 this.cachedMethodArguments[i] = descriptors[i]; 42 } 43 44 AutowiredAnnotationBeanPostProcessor.this.registerDependentBeans(beanName, autowiredBeans); 45 if (autowiredBeans.size() == paramTypes.length) { 46 Iterator<String> it = autowiredBeans.iterator(); 47 48 for(int ix = 0; ix < paramTypes.length; ++ix) { 49 String autowiredBeanName = (String)it.next(); 50 if (AutowiredAnnotationBeanPostProcessor.this.beanFactory.containsBean(autowiredBeanName) && AutowiredAnnotationBeanPostProcessor.this.beanFactory.isTypeMatch(autowiredBeanName, paramTypes[ix])) { 51 this.cachedMethodArguments[ix] = new ShortcutDependencyDescriptor(descriptors[ix], autowiredBeanName, paramTypes[ix]); 52 } 53 } 54 } 55 } else { 56 this.cachedMethodArguments = null; 57 } 58 59 this.cached = true; 60 } 61 } 62 } 63 64 if (arguments != null) { 65 try { 66 ReflectionUtils.makeAccessible(method); 67 method.invoke(bean, arguments); 68 } catch (InvocationTargetException var15) { 69 throw var15.getTargetException(); 70 } 71 } 72 73 } 74}

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

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

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

1// 2// Source code recreated from a .class file by IntelliJ IDEA 3// (powered by FernFlower decompiler) 4// 5 6package springfox.documentation.swagger.configuration; 7 8import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; 9//省略部分import 10@Configuration 11@ComponentScan( 12 basePackages = {"springfox.documentation.swagger.schema", "springfox.documentation.swagger.readers", "springfox.documentation.swagger.web"} 13) 14public class SwaggerCommonConfiguration { 15 public SwaggerCommonConfiguration() { 16 } 17 18 @Bean 19 public static PropertyPlaceholderConfigurer swaggerProperties() { 20 PropertyPlaceholderConfigurer propertiesPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); 21 propertiesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true); 22 return propertiesPlaceholderConfigurer; 23 } 24} 25

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

结论

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

  1. 如果不需要使用swagger了,直接去掉swagger配置即可
  2. 如果还需要使用swagger,升级swagger版本到2.7.0吧,2.7.0删掉了这个配置
点赞
收藏

评论区

加载中...

相关推荐

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

springcloud eureka.instance

1.在springcloud中服务的 InstanceID默认值是:${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance\_id:${server.port}},也就是:主机名:应用名:应用端口。如图1

Nacos配置中心动态获取数组配置

有的时候我们需要动态获取一系列的配置项,假设我们在nacos配置中心的配置如下,nacos配置中心的其他设置请参考Nacos搭建流程(https://my.oschina.net/u/3768341/blog/3138297)skill:name:爆炸冲刺在SpringCloud代

Spring Cloud Alibaba系列(二)nacos作为服务配置中心

SpringCloudAlibaba系列(二)nacos作为服务配置中心Nacos提供用于存储配置和其他元数据的key/value存储,为分布式系统中的外部化配置提供服务器端和客户端支持。使用SpringCloudAlibabaNacosConfig,您可以在NacosServer集中管理你SpringCloud应用的外部

GO值类型与引用类型

值类型值类型包括基本数据类型,int,float,bool,string,以及数组和结构体(struct)。值类型变量声明后,不管是否已经赋值,编译器为其分配内存,此时该值存储于栈上。值类型的默认值:varaint//int类型默认值为0varbstring//string类型默认值为n