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:ThirdFeignClientWhy 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:mvcResourceUrlProviderWhat is? whyThirdInterceptorConfigRely onmvcResourceUrlProvider?

Question3: WhymvcResourceUrlProviderAnd 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.singletonsCurrentlyInCreationIt 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/…