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中有个值获取的规则

#默认情况下,远程配置会优先于本地配置
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;
可以看到:
在这里插入图片描述
我们顺着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逻辑:
在这里插入图片描述
单步到下面
在这里插入图片描述
发现参数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的填充器是哪个配置装配进去的。
找到这里下个断点
在这里插入图片描述
方法签名

org.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

//
// 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删掉了这个配置

猜你喜欢

转载自blog.csdn.net/u013014691/article/details/125113121