Spring Interceptor 自動注入FeignClient致使循環依賴2.0

1,bug現場還原

問題描述

我在寫攔截器的時候,多個類都是經過構造器注入,而且也在攔截器中經過構造器顯示聲明瞭依賴FeignClient,在項目啓動後,Spring依賴分析顯示,這些類產生了循環依賴java

報錯信息


異常分析

thirdDemo是啓動類web

TakeResourcesClient是@Component註解的類,裏面經過 @Autowired調用ThirdFeignClientspring

@Component
public class TakeResourcesClient {
    @Autowired
    private ThirdFeignClient thirdFeignClient;

    @Autowired
    private ThirdProperties thirdProperties;
    
    ……
}複製代碼

這個能解釋循環依賴的依賴1和依賴2,SpringBoot在啓動的時候自動加載@Component,分析其依賴的ThirdFeignClientbash

@FeignClient(path = PathConstant.CONTEXT_PATH + PathConstant.URL, name = PathConstant.NAME_APPLICATION)
public interface ThirdFeignClient {
 
}複製代碼

這是ThirdFeignClient,是一個用@FeignClient註解的Feign客戶端mvc

接着往下,依賴3沒法解釋,這裏產生了app

問題1:ThirdFeignClient爲何會依賴WebMvcAutoConfiguration$EnableWebMvcConfigurationide

繼續往下,分析依賴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

2,bug分析

2.1假說

依賴分析的結果可能並非真正的依賴關係,而是在執行依賴分析的時候出發了某種異常,這個異常的核心是mvcResourceUrlProvider,而mvcResourceUrlProviderFeignClient加載和攔截器的加載順序有關,那麼要debug找到throw異常的第一現場,看看和mvcResourceUrlProvider有沒有關係。

2.2Debug

異常分析

異常第一現場以下


分析這段代碼的意思應該是:org.springframework.beans.factory.support.DefaultSingletonBeanRegistrygetSingleton()函數在建立mvcResourceUrlProvider以前,先調用beforeSingletonCreation()函數來校驗mvcResourceUrlProviderthis.singletonsCurrentlyInCreation中是否已經存在,若是存在則拋異常

繼續關注mvcResourceUrlProvider是在哪裏被初始化加載的


經過調用棧追溯,找到org.springframework.context.event.AbstractApplicationEventMulticasterretrieveApplicationListeners()函數,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()函數調用過程分析

第一次執行時,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依賴有關,找到以下 img

這裏驗證了依賴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(),把 mvcResourceUrlProvideradd進this.singletonsCurrentlyInCreation中,無異常


第二次執行this.singletonsCurrentlyInCreation()函數調用過程分析

有了第一次分析,debug第二次的時候,先關注是有什麼依賴引起FeignContext建立,以及爲何FeignContext須要再次建立

相同的追溯調用棧方式,找到依賴



如上兩圖,能夠獲得 ThirdFeignClient --> thirdInterceptorConfig --> WebMvcAutoConfiguration$EnableWebMvcConfiguration這樣的依賴關係,一樣的,會走到建立FeignContext的步驟


第二次執行beforeSingletonCreation(),把 mvcResourceUrlProvideradd進this.singletonsCurrentlyInCreation,觸發異常,也就是異常的第一現場。

分析:WebMvcAutoConfiguration$EnableWebMvcConfiguration應當是攔截器配置類,即ThirdInterceptorConfig ,構造器顯示聲明瞭 ThirdFeignClient 依賴,致使第二次建立FeignContext

那麼爲何爲何FeignContext須要再次建立?

FeignContext用於隔離配置的, 繼承org.springframework.cloud.context.named.NamedContextFactory, 就是上面的createContextcreateContext爲每一個命名空間獨立建立ApplicationContext,設置parent爲外部傳入的Context,這樣就能夠共用外部的Context中的Bean。

關注建立 FeignContext前對於命名空間的判斷,每次執行getContext()的時候,命令空間都是platform-3rd而已有的命名空間this.contexts數量都是0,這直接致使麼FeignContext建立兩次,每次都進去createContext()階段,應該是第一次執行以後 FeignContext並無真正存在this.contexts中。

3,分析

下圖時根據上面的分析,勾勒出的執行步驟觸發異常的流程圖


在這裏,這兩個步驟至關於同時發生,而且ThirdFeignClient都是被其餘自動裝配類經過構造器顯示聲明應用,致使兩次加載,我想,ThirdFeignClient是Feign的客戶端,不要顯示地經過構造器來注入,讓Spring容器去管理它的生成,其餘地方要調用就能夠了,不須要經過顯示聲明去初始化而致使建立FeignContext

採起措施,在調用ThirdFeignClient的類中經過@Autowired註解來調用

回答問題1:

第二次執行beforeSingletonCreation()的時候,應該是WebMvcAutoConfiguration$EnableWebMvcConfiguration依賴 ThirdFeignClient

回答問題2:

ThirdInterceptorConfig顯示依賴了ThirdFeignClient,致使建立FeignContextcontext.refresh()又加載了 mvcResourceUrlProvider

回答問題3:

mvcResourceUrlProvider不依賴ThirdFeignClient,是兩次加載 FeignContext觸發的異常

4,實現

改動後代碼以下

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了。


5,結論

Feign客戶端Spring去分析依賴,不要經過構造器注入,在調用的時候經過@Autowired註解來調用。

參考文檔

techblog.ppdai.com/2018/05/28/…

相關文章
相關標籤/搜索