我在寫攔截器的時候,多個類都是經過構造器注入,而且也在攔截器中經過構造器顯示聲明瞭依賴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註解來調用。
參考文檔