origin
A new function has been added. @Value("${xxx.aaa:b}")
This form is used to obtain a variable. The default value b is assigned a default value when the corresponding value cannot be found in the configuration. The project also integrates Nacos, which is configured in Nacos. After startup, it is found to xxx.aaa=c
obtain The value of is the default, not Nacos. When the default value is removed, the @Value("${xxx.aaa}")
value of Nacos can be obtained correctly.
version agreement
Spring Cloud version Dalston.SR4
Swagger2 version 2.6.0
analyze
1. Is it a problem with Nacos?
Obviously not, because after removing the default value, the value configured in Nacos can be obtained correctly, indicating that the value of Nacos can be obtained, but it is not the first priority
2. Check whether it is a value acquisition sequence problem
There is a rule for value acquisition in Spring Boot
#默认情况下,远程配置会优先于本地配置
spring.cloud.config.allowOverride
spring.cloud.config.overrideNone
spring.cloud.config.overrideSystemProperties
These values are default, and it is not configured in the configuration, so the remote takes precedence over the default value
process
@Value
This annotation can be added to the class attribute or to the method, because the change of the class attribute is not easy to monitor, so write a variable to be assigned and setter
add the annotation to setter
it
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();
}
So, will
@Value("${mock.open:N}")
private String mockOpen;
changed to
@Value("${mock.open:N}")
public void setMockOpen(String mockOpen) {
this.mockOpen = mockOpen;
}
Then add a breakpoint in this.mockOpen = mockOpen;
this sentence, the configuration file configuration corresponding to Nacos mock.open=Y
. Start the service and wait for the endpoint to stay at . this.mockOpen = mockOpen;
You can see:
We followed setMockOpen
the method stack online to find this place in the figure and found that the parameter passed in is , which means that this value N
has been obtained by the time this step is reached . The signature of this method is:N
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement#inject
Looking up the parameter in the figure above arguments
, you can see that this parameter is calculated internally, then the calculation logic is in the current method, and the method logic is as follows:
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();
}
}
}
}
There are two places where the above code may be obtained, or if
one else
of the two branches will be obtained. We Method method = (Method)this.member;
add a breakpoint to this line of code after the method enters, set conditions for the breakpoint beanName.equalsIgnoreCase("ESAttribute")
, ESAttribute
which is setMockOpen(String mockOpen)
the class where the method is located, and then restart the service .
After the breakpoint takes effect, the single-step debugging found that it has entered else
the logic:
step to the bottom and
find that the parameter arg
value is N
, and there is an assignment statement below arguments[ixx] = arg;
, which is most likely to be this,
discard it to the current call stack, re-enter, and go to the single line in the figure Enter the method step by step,
then
go here (method signature org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency
) to continue to enter the method
and then
the method signature org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
, the method in the above figure enters the method in a single step , and you can see the calculated value in the first cycle of the cycle
here . Check the current Property parser , I found that the filler is actually , meow, why is it not Spring's own Property filler, why is it a filler, so I checked this The second is Spring's own Property filler, then go to Take a look at the order in which the container is started, and check which configuration the swagger filler is assembled in by the way. Find the next breakpoint method signature heredo while
result
N
swaggerProperties
swagger
this.embeddedValueResolvers
this.embeddedValueResolvers
org.springframework.beans.factory.support.AbstractBeanFactory#addEmbeddedValueResolver
After restarting the application, the first one that comes in is
indeed Spring’s own, and then let the program continue. The second one that comes in is
Yes swaggerProperties
, the assembled class
is The method to find the assembled beanFactory
class can be seen in beanDefinitionMap
the execution expression below Information, found from , this class. But the strange thing is that at this time it is actually 0 , and then the program continues to run, and this time it is Spring’s Property filler (I compared the Spring’s filler that came in for the first time with the Spring’s filler that came in this time, for example Not the same one, which means that the one that came in for the first time was deleted. As for the reason, I didn’t go into the details. I personally guess that the first one may be put in by Spring Boot. Because Spring Cloud supports dynamic configuration like Nacos, the extension enhances Spring Boot’s parser, delete Spring Boot’s when assembling, and put in the enhanced one), I will not post the screenshot for the third time I come in. At this time, there are two fillers in , namely swagger and spring, swagger comes first, which leads to the problem at the beginning of the article. So let's look at this class((DefaultListableBeanFactory)((PropertyPlaceholderConfigurer)((PlaceholderResolvingStringValueResolver)valueResolver).this$0).beanFactory).beanDefinitionMap.get("swaggerProperties")
swaggerProperties
beanDefinition
springfox.documentation.swagger.configuration.SwaggerCommonConfiguration
this.embeddedValueResolvers
this.embeddedValueResolvers
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;
}
}
Good guy, one is defined directly PropertyPlaceholderConfigurer
, and this PropertyPlaceholderConfigurer
is still Spring, I don’t know why.
in conclusion
From the analysis so far, we can see that the problem has been found, then the solution is ready to come out
- If you don't need to use swagger, just remove the swagger configuration
- If you still need to use swagger, upgrade the swagger version to 2.7.0, 2.7.0 deletes this configuration