缘起
新增了一个功能,使用了@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")
,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的填充器是哪个配置装配进去的。
找到这里下个断点
方法签名
org.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
//
// 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的,不知道为啥。
结论
分析至此,可以看到已经发现问题所在了,那么解决方案就呼之欲出了
- 如果不需要使用swagger了,直接去掉swagger配置即可
- 如果还需要使用swagger,升级swagger版本到2.7.0吧,2.7.0删掉了这个配置