我在写拦截器的时候,多个类都是经过构造器注入,而且也在拦截器中经过构造器显示声明了依赖FeignClient,在项目启动后,Spring依赖分析显示,这些类产生了循环依赖java
thirdDemo
是启动类web
TakeResourcesClient
是@Component注解的类,里面经过 @Autowired调用ThirdFeignClient
spring
@Component
public class TakeResourcesClient {
@Autowired
private ThirdFeignClient thirdFeignClient;
@Autowired
private ThirdProperties thirdProperties;
……
}复制代码
这个能解释循环依赖的依赖1和依赖2,SpringBoot在启动的时候自动加载@Component,分析其依赖的ThirdFeignClient
bash
@FeignClient(path = PathConstant.CONTEXT_PATH + PathConstant.URL, name = PathConstant.NAME_APPLICATION)
public interface ThirdFeignClient {
}复制代码
这是ThirdFeignClient
,是一个用@FeignClient注解的Feign客户端mvc
接着往下,依赖3没法解释,这里产生了app
问题1:ThirdFeignClient
为何会依赖WebMvcAutoConfiguration$EnableWebMvcConfiguration
?ide
继续往下,分析依赖4函数
ThirdInterceptorConfig
是拦截器配置类,继承了WebMvcConfigurationSupport
,构造器注入了ThirdFeignClient
的依赖ui
@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))
……
}复制代码
可是这里会有断层,依赖2是TakeResourcesClient
--> ThirdFeignClient
(经过 @Autowired调用ThirdFeignClient)this
依赖4经过 构造器注入ThirdFeignClient
,应该也是 ThirdInterceptorConfig
--> ThirdFeignClien
最后看一下拦截器的配置,也是经过构造器注入ThirdFeignClient
,其实ThirdInterceptorConfig
要注入ThirdFeignClient
,目的就是为了在生成ThirdInterceptor
对象的时候,注入ThirdFeignClient
拦截器
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;
}
……复制代码
继续往下,依赖5和依赖6也没法解释,那么产生了以下几个问题
问题2:mvcResourceUrlProvider
是什么?为何ThirdInterceptorConfig
依赖mvcResourceUrlProvider
?
问题3:为何mvcResourceUrlProvider
又依赖ThirdFeignClient
?
依赖分析的结果可能并非真正的依赖关系,而是在执行依赖分析的时候出发了某种异常,这个异常的核心是mvcResourceUrlProvider
,而mvcResourceUrlProvider
和FeignClient
加载和拦截器的加载顺序有关,那么要debug找到throw异常的第一现场,看看和mvcResourceUrlProvider
有没有关系。
异常第一现场以下
分析这段代码的意思应该是:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
的getSingleton()
函数在建立mvcResourceUrlProvider
以前,先调用beforeSingletonCreation()
函数来校验mvcResourceUrlProvider
在this.singletonsCurrentlyInCreation
中是否已经存在,若是存在则抛异常
继续关注mvcResourceUrlProvider
是在哪里被初始化加载的
经过调用栈追溯,找到org.springframework.context.event.AbstractApplicationEventMulticaster
的retrieveApplicationListeners()
函数,mvcResourceUrlProvider
在这里第一次出现,是listenerBeans
中的一个元素,而listenerBeans
是
listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);复制代码
初始化赋值出来的,listenerBeans
的所有对象有22个,看起来像是SpringBoot默认初始化的实例。
搜了一下这个类,确实是缺省配置,是Springboot Web应用启动过程当中定义的Bean。参考 blog.csdn.net/andy_zhang2…
继续追问:为何this.singletonsCurrentlyInCreation
中已经存在了mvcResourceUrlProvider
,确定是有其余地方加载的,先全局搜一下mvcResourceUrlProvider
,在org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
中
被直接调用的地方只有一处,也是在org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
中
这里应该是WebMvcConfigurationSuppor
在添加完拦截器以后,经过@Bean注解去调用mvcResourceUrlProvider
注册成为默认拦截器,而mvcResourceUrlProvider
已经做为缺省配置被预先加载好了。
(mvcResourceUrlProvider
提供ResourceUrlProvider
实例,ResourceUrlProvider
是获取外部URL路径的转换的核心组件,其内部定了Map<String, ResourceHttpRequestHandler> handlerMap
用来进行链式的解析。)
至此,要先解决的问题是
为何this.singletonsCurrentlyInCreation
中已经存在了mvcResourceUrlProvider
?
在beforeSingletonCreation()
打断点发现,此函数会被执行两次,第一次执行时,this.singletonsCurrentlyInCreation
中没有mvcResourceUrlProvider
,不会触发异常,第二次才会触发异常
第一次执行时,this.singletonsCurrentlyInCreation
中没有mvcResourceUrlProvider
,而后把mvcResourceUrlProvider
加进去,这样第二次执行的时候就会触发异常
如今不知道为何beforeSingletonCreation()
函数会执行两次,看这个函数和相关命名,是不该该被加载两次的。经过观察调用栈,发现跟refresh事件发布有关,看一下调用栈中的refresh()
函数,
位于org.springframework.context.support.AbstractApplicationContext
中,这应该是context建立阶段的一个步骤。
refresh()
调用栈的后面紧接着就是createContext()
,位于org.springframework.cloud.context.named.NamedContextFactory
中,这个函数里面执行了context.refresh()
,那么context为何会建立,经过调用栈和context的属性,判断这应该是FeignContext
,以下
如今提出一个假说:在解析自动配置的时候,Spring分析依赖,扫描到了跟Feign相关的依赖,认为有必要建立FeignContext,建立过程当中执行了context.refresh()
根据beanName相关信息,追溯堆栈到feign相关函数以前,找到跟Feign相关的依赖,以下
经过函数名和相关变量就能看出来,这是从FeignClientFactoryBean
这个工厂Bean中获取ThirdFeignClient
实例,参考spring-cloud-openfeign原理分析,确认FeignClientFactoryBean 建立feign客户端的工厂。
追溯调用栈,继续分析是什么自动配置会跟Feign依赖有关,找到以下
这里验证了依赖2,和上面假说的前半段,Spring装载自动配置类TakeResourcesClient
,找到它依赖ThirdFeignClient
。
这里继续关注一下doGetObjectFromFactoryBean()
,看看FeignClient建立过程
Feign.Builder builder = feign(context);复制代码
这段代码的执行会调用其余函数,建立FeignContext,位于org.springframework.cloud.context.named.NamedContextFactory
以下,这里建立FeignContext时候执行了context.refresh()
,和前面的refresh()
函数执行match上了,而且refresh()
以后,会第一次执行beforeSingletonCreation()
,把 mvcResourceUrlProvider
add进this.singletonsCurrentlyInCreation
中,无异常
有了第一次分析,debug第二次的时候,先关注是有什么依赖引起FeignContext建立,以及为何FeignContext须要再次建立
相同的追溯调用栈方式,找到依赖
如上两图,能够获得 ThirdFeignClient
--> thirdInterceptorConfig
--> WebMvcAutoConfiguration$EnableWebMvcConfiguration
这样的依赖关系,一样的,会走到建立FeignContext的步骤
第二次执行beforeSingletonCreation()
,把 mvcResourceUrlProvider
add进this.singletonsCurrentlyInCreation
中,触发异常,也就是异常的第一现场。
分析:WebMvcAutoConfiguration$EnableWebMvcConfiguration
应当是拦截器配置类,即ThirdInterceptorConfig
,构造器显示声明了 ThirdFeignClient
依赖,致使第二次建立FeignContext
那么为何为何FeignContext须要再次建立?
FeignContext用于隔离配置的, 继承org.springframework.cloud.context.named.NamedContextFactory
, 就是上面的createContext
,createContext
为每一个命名空间独立建立ApplicationContext
,设置parent为外部传入的Context,这样就能够共用外部的Context中的Bean。
关注建立 FeignContext前对于命名空间的判断,每次执行getContext()
的时候,命令空间都是platform-3rd而已有的命名空间this.contexts数量都是0,这直接致使么FeignContext建立两次,每次都进去createContext()
阶段,应该是第一次执行以后 FeignContext并无真正存在this.contexts中。
下图时根据上面的分析,勾勒出的执行步骤触发异常的流程图
在这里,这两个步骤至关于同时发生,而且ThirdFeignClient
都是被其余自动装配类经过构造器显示声明应用,致使两次加载,我想,ThirdFeignClient
是Feign的客户端,不要显示地经过构造器来注入,让Spring容器去管理它的生成,其余地方要调用就能够了,不须要经过显示声明去初始化而致使建立FeignContext。
采起措施,在调用ThirdFeignClient
的类中经过@Autowired注解来调用
回答问题1:
第二次执行beforeSingletonCreation()
的时候,应该是WebMvcAutoConfiguration$EnableWebMvcConfiguration
依赖 ThirdFeignClient
回答问题2:
ThirdInterceptorConfig
显示依赖了ThirdFeignClient
,致使建立FeignContext,context.refresh()
又加载了 mvcResourceUrlProvider
回答问题3:
mvcResourceUrlProvider
不依赖ThirdFeignClient
,是两次加载 FeignContext触发的异常
改动后代码以下
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;
}
}
复制代码
@Component
public class TakeResourcesClient {
@Autowired
private ThirdFeignClient thirdFeignClient;
@Autowired
private ThirdProperties thirdProperties;
}复制代码
@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() {
return new ThirdInterceptor(authHandles, thirdProperties);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getThirdInterceptor())
……
}复制代码
改过以后,项目正常启动,是可行的。
而且观察加载顺序,在第一次加载 takeResourcesClient
实例的时候,已经加载了thirdFeignClient
实例,在加载 thirdInterceptorConfig
,执行
ConstructorResolver.setCurrentInjectionPoint(descriptor)复制代码
拿到previousInjectionPoint先前注入点,里面thirdFeignClient
,不会再建立FeignContext了。
Feign客户端Spring去分析依赖,不要经过构造器注入,在调用的时候经过@Autowired注解来调用。
参考文档