1. Bug site restoration
Problem description
When I wrote the interceptor, several classes were injected through the constructor and FeignClient dependencies were declared in the interceptor through the constructor display. After the project started, Spring dependency analysis showed that these classes generated cyclic dependencies
Error message
Abnormal analysis
ThirdDemo is the startup class
TakeResourcesClient is an @Component annotated class that calls ThirdFeignClient via @AutoWired
@Component
public class TakeResourcesClient {
@Autowired
private ThirdFeignClient thirdFeignClient;
@Autowired
privateThirdProperties thirdProperties; ... }Copy the code
SpringBoot automatically loads @Component at startup and analyzes its dependency ThirdFeignClient
@FeignClient(path = PathConstant.CONTEXT_PATH + PathConstant.URL, name = PathConstant.NAME_APPLICATION)
public interface ThirdFeignClient {}Copy the code
This is ThirdFeignClient, which is a Feign client annotated with @FeignClient
And then we go down, dependence 3 is unexplainable, and here it happens
Question 1:ThirdFeignClient
Why do we rely onWebMvcAutoConfiguration$EnableWebMvcConfiguration
?
Moving on, we look at dependency 4
ThirdInterceptorConfig is configured interceptor class, inherited WebMvcConfigurationSupport, constructor injection ThirdFeignClient dependence
@Component
public class ThirdInterceptorConfig extends WebMvcConfigurationSupport {
private final List<AuthHandle> authHandles;
private final ThirdProperties thirdProperties;
private final ThirdFeignClient thirdFeignClient;
@Autowired
public ThirdInterceptorConfig(List<AuthHandle> authHandles, ThirdProperties thirdProperties, ThirdFeignClient thirdFeignClient) {
this.authHandles = authHandles;
this.thirdProperties = thirdProperties;
this.thirdFeignClient = thirdFeignClient;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ThirdInterceptor(authHandles, thirdProperties, thirdFeignClient))
……
}Copy the code
But there is a disconnect here, dependency 2 is TakeResourcesClient –> ThirdFeignClient (call ThirdFeignClient via @autoWired)
Dependency 4 injects ThirdFeignClient into the constructor. ThirdInterceptorConfig –> ThirdFeignClien
The ThirdFeignClient constructor is also used to inject ThirdFeignClient into the ThirdInterceptorConfig constructor. The purpose of injecting ThirdFeignClient into the ThirdInterceptorConfig constructor is to generate a ThirdInterceptor object. Injection ThirdFeignClient
The interceptor
public class ThirdInterceptor extends HandlerInterceptorAdapter {
private final List<AuthHandle> authHandles;
private final ThirdProperties thirdProperties;
private ThirdFeignClient thirdFeignClient;
public ThirdInterceptor(List<AuthHandle> authHandles, ThirdProperties thirdProperties, ThirdFeignClient thirdFeignClient) {
this.authHandles = authHandles;
this.thirdProperties = thirdProperties;
this.thirdFeignClient = thirdFeignClient; }...Copy the code
Moving on, dependencies 5 and 6 are also unexplained, which raises the following questions
Question 2:mvcResourceUrlProvider
What is? whyThirdInterceptorConfig
Rely onmvcResourceUrlProvider
?
Question3: WhymvcResourceUrlProvider
And rely onThirdFeignClient
?
2. Bug analysis
2.1 hypothesis
The result of the dependency analysis may not be a real dependency, but some kind of exception is raised during the dependency analysis. The core of this exception is the mvcResourceUrlProvider, and the mvcResourceUrlProvider is related to the FeignClient load and the interceptor load order. Debug the first scene where the throw exception is found to see if there is a relationship with the mvcResourceUrlProvider.
2.2 the Debug
Abnormal analysis
The first scene of the exception is as follows
Parsing this code should mean: Org. Springframework. Beans. Factory. Support. The DefaultSingletonBeanRegistry getSingleton before creating mvcResourceUrlProvider () function, First call beforeSingletonCreation () function to check mvcResourceUrlProvider in this. SingletonsCurrentlyInCreation whether already exists, if there is abnormal
Where is the mvcResourceUrlProvider initialized to load
Through the call stack trace, find the org. Springframework. Context. Event. The AbstractApplicationEventMulticaster retrieveApplicationListeners () function, The mvcResourceUrlProvider first appears here as an element in listenerBeans, which is
listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);Copy the code
The total number of listenerBeans objects assigned by initialization is 22, which looks like instances of default SpringBoot initialization.
A search of this class shows that it is indeed the default configuration and is the Bean defined during the startup of the Springboot Web application. Reference blog.csdn.net/andy_zhang2…
Follow up: Why this. Already exists in the singletonsCurrentlyInCreation mvcResourceUrlProvider, certainly there are other local loading, the global search mvcResourceUrlProvider first, In the org. Springframework. Web. Servlet. Config. The annotation. WebMvcConfigurationSupport
Is only one direct call, is also in the org. Springframework. Web. Servlet. Config. The annotation. WebMvcConfigurationSupport
Here should be WebMvcConfigurationSuppor in after adding the interceptors, through @ Bean annotations to call mvcResourceUrlProvider registered as default interceptor, The mvcResourceUrlProvider is preloaded as the default configuration.
MvcResourceUrlProvider provides instances of ResourceUrlProvider, which is the core component of the transformation to get the external URL path. Its internal set a Map < String, ResourceHttpRequestHandler > handlerMap used for parsing of the chain.)
At this point, the first problem to solve is
whythis.singletonsCurrentlyInCreation
It already exists inmvcResourceUrlProvider
?
In beforeSingletonCreation () break points found, this function will be executed twice, the first execution, enclosing singletonsCurrentlyInCreation no mvcResourceUrlProvider, not trigger is unusual, The exception will only be raised the second time
For the first time to perform this. SingletonsCurrentlyInCreation () function call process analysis
Performed for the first time, this. No mvcResourceUrlProvider singletonsCurrentlyInCreation, then add mvcResourceUrlProvider inside, this will trigger the perform a second time
It is not clear why the beforeSingletonCreation() function is executed twice, but this function and its associated name should not be loaded twice. If you look at the call stack and see that it is related to refresh event publishing, look at the refresh() function in the call stack,
Located in the org. Springframework. Context. Support. AbstractApplicationContext, this should be the context to create phase of a step.
Refresh () followed by the call stack is createContext (), located in the org. Springframework. Cloud. Context. Named. NamedContextFactory, Refresh (), so why is the context created? By calling the stack and the properties of the context, it should be FeignContext, as follows
Here’s a hypothesis: While parsing the auto-configuration, Spring analyzes the dependencies, scans for Feign dependencies, and deems it necessary to create FeignContext. Context.refresh() is executed during the creation process.
Trace the stack back to the feIGN dependent function based on the beanName information to find the feIGN dependent, as shown below
It can be seen from the function name and related variables. This is to obtain the ThirdFeignClient instance from FeignClientFactoryBean. Refer to spring-Cloud-OpenFeign principle analysis. Verify that FeignClientFactoryBean creates the feign client factory.
Trace the call stack to see what auto-configuration is associated with Feign dependencies and find the following
Verifying dependency 2, and the first half of the hypothesis above, Spring loads the auto-configuration class TakeResourcesClient and finds that it relies on The ThirdFeignClient.
Continue to focus on here doGetObjectFromFactoryBean () to see FeignClient creation process
Feign.Builder builder = feign(context);Copy the code
The execution of this code can call other functions, create FeignContext, located in the org. Springframework. Cloud. Context. Named. NamedContextFactory
Refresh () and beforeSingletonCreation() are executed for the first time after refresh() MvcResourceUrlProvideradd into this. SingletonsCurrentlyInCreation, without exception
Second execution enclosing singletonsCurrentlyInCreation () function call process analysis
With the first analysis, debug the second time, focusing on what dependencies caused the FeignContext to be created, and why the FeignContext needs to be created again
Trace the call stack the same way to find dependencies
As above two figure, can get ThirdFeignClient – > thirdInterceptorConfig – > WebMvcAutoConfiguration $EnableWebMvcConfiguration such dependencies, in the same way, It goes to the step of creating the FeignContext
The second execution beforeSingletonCreation (), mvcResourceUrlProvideradd into this. SingletonsCurrentlyInCreation, trigger the exception, the first scene is abnormal.
Analysis: WebMvcAutoConfiguration $EnableWebMvcConfiguration should be a blocker configuration classes, namely ThirdInterceptorConfig, constructor according to a statement ThirdFeignClient dependence, Causes FeignContext to be created a second time
So why why does FeignContext need to be created again?
FeignContext to segregate configuration, inherit org. Springframework. Cloud. Context. Named. NamedContextFactory, is above createContext, CreateContext creates an ApplicationContext independently for each namespace and sets parent to the external Context so that beans in the external Context can be shared.
The command space is platform-3rd each time getContext() is executed. The number of this.contexts is 0. FeignContext is created twice. FeignContext does not actually exist in this.contexts after the first execution.
3, the analysis
The following figure shows a flowchart for executing steps that trigger exceptions based on the above analysis
In this case, both of these steps are essentially happening at the same time, and ThirdFeignClient is applied by other autoloader classes with constructor display declarations, resulting in two loads. ThirdFeignClient is a Feign client, SO don’t explicitly inject it through the constructor. Let the Spring container manage its generation and call it elsewhere, without having to initialize the FeignContext by displaying the declaration.
Take steps to call ThirdFeignClient via the @AutoWired annotation in the class that calls it
Answer question 1:
The second execution beforeSingletonCreation (), should be WebMvcAutoConfiguration $EnableWebMvcConfiguration ThirdFeignClient
Answer question 2:
ThirdInterceptorConfig displays a dependency on ThirdFeignClient, causing FeignContext to be created, and context.refresh() loads the mvcResourceUrlProvider
Answer question 3:
MvcResourceUrlProvider does not rely on ThirdFeignClient and is an exception that is triggered when FeignContext is loaded twice
4, implement
The modified code is as follows
public class ThirdInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory.getLogger(ThirdInterceptor.class);
private final List<AuthHandle> authHandles;
private final ThirdProperties thirdProperties;
@Autowired
private ThirdFeignClient thirdFeignClient;
public ThirdInterceptor(List<AuthHandle> authHandles, ThirdProperties thirdProperties) {
this.authHandles = authHandles;
this.thirdProperties = thirdProperties; }}Copy the code
@Component
public class TakeResourcesClient {
@Autowired
private ThirdFeignClient thirdFeignClient;
@Autowired
private ThirdProperties thirdProperties;
}Copy the code
@Configuration
public class ThirdInterceptorConfig extends WebMvcConfigurationSupport {
private final List<AuthHandle> authHandles;
private final ThirdProperties thirdProperties;
@Autowired
public ThirdInterceptorConfig(List<AuthHandle> authHandles, ThirdProperties thirdProperties) {
this.authHandles = authHandles;
this.thirdProperties = thirdProperties;
}
@Bean
public ThirdInterceptor getThirdInterceptor() {
returnnew ThirdInterceptor(authHandles, thirdProperties); } @override public void addInterceptors(InterceptorRegistry registry) {registry. AddInterceptor (getThirdInterceptor())... ... }Copy the code
After the modification, the project started normally and was feasible.
In addition, observe the loading sequence. When the takeResourcesClient instance is loaded for the first time, the thirdFeignClient instance has been loaded
ConstructorResolver.setCurrentInjectionPoint(descriptor)Copy the code
Get the previousInjectionPoint, thirdFeignClient, so I’m not going to create the FeignContext anymore.
Five, the conclusion
Feign client Spring parses dependencies, not via constructor injection, but via @Autowired annotation when called.
Reference documentation
Techblog.ppdai.com/2018/05/28/…