缘起
新增了一个功能,使用了@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"),ESAttribute是setMockOpen(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")可以看到swaggerProperties的beanDefinition信息,发现来自于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的,不知道为啥。
结论
分析至此,可以看到已经发现问题所在了,那么解决方案就呼之欲出了
- 如果不需要使用swagger了,直接去掉swagger配置即可
- 如果还需要使用swagger,升级swagger版本到2.7.0吧,2.7.0删掉了这个配置