Feign集成Hystrix源碼分析以及擴展

前言
在《Hystrix源碼淺析》中分析過request cache的相關源碼實現,而在Feign中集成了RibbonHystrix兩個重要組件,但很遺憾其默認設計中並沒有對hystrix request cache的實現。故而本章節將從源碼出發,瞭解Feign是如何集成Hystrix組件的,以便後續我們擴展Hystrix在Feign中的應用。正文部分將分爲4個階段分析,爲何是4個階段呢,看了就明白啦。


正文

第一階段
直接切入正題,找到FeignClient的自動配置類FeignClientsConfiguration,編解碼器均在當前配置類中設定,我們關注的feign(此處Feign.Builder最終將幫助我們創建feign實例)客戶端定義如下

如上圖,可以看到其中有一個內部類HystrixFeignConfiguration,其註冊有兩個條件:
  • 在classpath中找到HystrixCommandHystrixFeign類;
  • 同時在feignHystrixBuilder方法上可以看到其還需要feign.hystrix.enabled配置爲true(如果沒有配置,默認值爲false);
只有滿足了以上兩個條件的,方會註冊含有ystrix的feign客戶端實例,在一個應用中我們會定義多個feignClient,我們需要設定Bean的scopeprototype
反之,如果沒有同時滿足上述兩個條件,將會默認註冊一個常規的FeignBuilder實例。
在源碼中還有FeignAutoConfiguration作爲feign的自動配置,

紅色框中已經標出了重點,很明顯兩個自動裝配過程通過“feign.hystrix.HystrixFeign”類是否在classpath中作爲判斷依據形成了一個互斥約定,貼合本章重點,我們已經加入了Hystrix相關的依賴,故其會自動裝配HystrixTargeter實例。進入HystrixTargeter類中,其核心實現在target方法中,

通過4個紅色框標記了我們需要分析的四點:
  • 根據之前的分析,我們當前裝載的builderHystrixFeign.builder,可直接轉型;
  • 先記住此處的setterfactory設定,其將在構造HystrixCommand時起到關鍵作用,如下是一個Default實現:

在Feign類中定義了生成commandKey的實現,其主要依據feignclient實例和method生成,由‘類名稱#方法名+參數列表中參數類型’組成;
  • 優先獲取FeignClientFactoryBeanfallback定義,如果獲取到則直接通過fallback定義類生成包含回退方法的feignClient代理;
  • 如果沒有定義fallback類,繼續獲取FeignClientFactoryBeanfallbackFactory定義,如果獲取到則直接通過fallbackFactory定義類生成包含回退方法的feignClient代理;
通過上述的回退方法邏輯我們可以看到fallbackfallbackFactory的優先級,fallback優先級高於fallbackFactory

第二階段
問題來了,target方法又是何時被調用的呢?我們從啓動類中添加的@EnableFeignClients入手,通過@EnableFeignClients註解啓用聲明式客戶端應用,

FeignClientsRegistrar實現了ImportBeanDefinitionRegistrar接口,從而具備了手動註冊Bean的能力,從名稱上來看所有的FeignClient實例將通過FeignClientsRegistrar來實現註冊,
如上圖核心方法registerBeanDefinitions在接口中的描述,其中還提及了BeanDefinitionRegistryPostProcessor,該接口同樣可以實現bean的註冊以及對已經註冊完成的bean進行調整(其主要作用是對已註冊bean實現擴展)。直接來看看重寫的核心方法registerBeanDefinitions吧,
其定義了兩個步驟,
  • 首先通過registerDefaultConfiguration方法設定默認的配置,可以看到其會註冊FeignClientSpecification類型實例,究竟這部分會在何時使用呢,後續將會有涉及,
  • 其次通過registerFeignClients方法來註冊聲明式客戶端feignClient,核心代碼如下:
根據上述的斷言,我們也可以明白爲何聲明式客戶端的定義是通過接口來完成的,通過對指定的package掃描獲取到所有的@FeignClient註解定義的beanDefinition,如本工程目前配置了6個如下:

繼續關注紅色框部分,
  • 通過@FeignClient註解獲取到所有的配置屬性以Map方式存儲在attributes中。
  • 通過registerClientConfiguration(registry, name,attributes.get("configuration"));方法根據configuration屬性獲取到每個feignClient設定的配置類。
  • 通過registerFeignClient(registry, annotationMetadata, attributes);方法註冊每個feignClient;

registerFeignClient方法中可以看到@FeignClient註解中所有的參數定義,均將作爲後續註冊bean實例的屬性,並設定自動裝配模式爲類型裝配。
通過最後一個紅色框即可完成bean的註冊(註冊並不代表實例化完成),先來看看其定義,

registerBeanDefinition方法即可完成最後一步註冊工作,通過下圖來看看definitionHolder的定義

beanName爲接口全名稱,並根據上游registerFeignClient方法獲取到了別名,需要關注此處的beanDefinition中class定義爲FeignClientFactoryBean,這正是FactoryBean的作用,FactoryBean將通過getObject生產指定的bean實例,是工廠模式的一個實現。

第三階段
通過以上完成了每個feignClient對應的FeignClientFactoryBean註冊。既然是通過工廠bean來實例化對應的實例,那麼下面我們來看看getObject()方法又是如何被調用,並生成真實的Bean實例的呢,先來看一段代碼

Controller中我們通過@Autowired注入了Service1FeignClient,在服務啓動過程中,DefaultListableBeanFactory(最核心的bean實例化實現)將實例化FeignConsumerController的bean實例,其調用鏈路如下(由下往上)

找到preInstantiateSingletons方法,被實例化bean描述如下

其中調用了AbstractBeanFactorygetBean方法獲取當前的feignConsumerController的bean實例。
DefaultListableBeanFactory類是整個IOC容器初始化的核心,爲了後續更好的理解類、方法間的關係,通過下圖來了解下DefaultListableBeanFactory類關係圖

接上getBean()方法繼續往下走,找到DefaultSingletonBeanRegistrygetSingleton()方法獲取單例,可以看到其中有一些cache判斷,具體cache定義如下,以map存儲爲主,beanname作爲key值,

如果已經創建對應的實例則會保存在cache中,直接通過singletonObjects.get(beanName)獲取返回即可,首次不存在則需要執行後續邏輯完成創建,找到AbstractAutowireCapableBeanFactorypopulateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw)方法,

其中定義了依賴注入的類型的判斷,通過調用的方法堆棧可以看到,在註冊完成當前bean後會繼續分析其依賴的實例,這也正是依賴注入的核心,在DefaultListableBeanFactory類中resolveDependency方法調用findAutowireCandidates方法找到依賴的候選者,然後對依賴的候選者進行註冊實例過程,最終在AbstractBeanFactory類的getObjectForBeanInstance方法中通過一系列判斷後調用父類FactoryBeanRegistrySupport中的getObjectFromFactoryBean方法-->doGetObjectFromFactoryBean方法,調用如下(其中factory即爲FeignClientFactoryBean實例),


第四階段
此處終於回到上面提到的問題,getObject方法是何時被調用的呢?其是在註冊相關調用bean實例後,分析依賴bean實例,然後至cache中查找是否已經完成實例化,如果沒有實例化則繼續通過getBean()方法完成其對應的實例化過程,此處是通過FactoryBean方式獲取實例,繼續來看看FeignClientFactoryBean中是如何實現的呢?

終於看到最開始我們註冊實例化的Feign.Builder實例,feign方法定義如下

紅色框中的內容來自於@FeignClient註解中configuration配置類中的定義,其定義在FeignAutoConfiguration自動裝配類中

FeignClientSpecification正是來源於FeignClientsRegistrar中的registerDefaultConfiguration(metadata, registry);方法實現,繼續來看看loadBalance方法的定義

此時的target實例即爲我們最初裝配實例化的HystrixTargetertarget方法實現如下

  • 第一個紅色框很好理解,強轉爲我們的HystrixFeign.Builder實例;
  • 第二個紅色框可能看着比較默認,我們來看下其又是如何定義的:

實現了一個默認的Default內部類,通過實現的create方法可以看到,其主要是定義了HystrixCommandSetter參數。這裏我們可以再理解下FeignContext的作用,其協助我們實現了多個FeignClientconfig配置隔離,故類似的SetterFactory對應的bean實例均會配置在@FeignClientconfiguration參數對應的配置類中,從而實現隔離。如果沒有對應的配置將採用默認值,默認值設定在HystrixFeign類中

  • 通過第三個和第四個紅色框可以看到,其定義了fallback的實現優先級,如果沒有定義對應的fallback則直接進入默認實現。
我們來看看fallbackFactory對應的target實現

如上在有fallback定義下,通過設定動態代理實現集成Hystrix應用,

可以看到上述配置定義的SetterFactory在此處通過構造函數傳入HystrixInvocationHandler實例中,HystrixInvocationHandler是JDK動態代理的實現,通過切面技術對待執行方法實現HystrixCommand的應用,重寫實現fallback功能,

無論是否定義fallback,均會執行父類build

如果沒有fallback定義則invocationHandlerFactory採用默認定義,

真正的返回值在ReflectiveFeign中的newInstance被返回,其值爲我們定義的FeignClient,繼續來看看源碼定義

  • 第一個紅色框methodToHandler正是後續傳入invocationHandler中的重要參數,其定義了feignClient下所有符合條件的method,如下dispatch就是此處的methodToHandler參數,其對每個方法設定了對應的回退方法和setterFactory參數,用於實例化HystrixCommand以及調用fallback方法:

  • 第二個紅色框中根據一定條件判斷遍歷載入當前接口中定義的方法至methodToHandler中;
  • 第三個紅色框使用JDK動態代理構建了代理對象proxy
如果我們定義了多個FeignClient接口,其均會在@Autowired注入時重複通過FeignClientFactoryBean創建。


總結
通過上面的源碼分析,可以看到,其核心源於spring ioc的實現原理。通過動態代理實現對方法的切面處理,從而集成Hystrix。

擴展
正如前言中描述,本章源碼的跟蹤的目的是爲了在Feign中實現request cache。可以看到在HystrixInvocationHandlerinvoke方法實現中,其通過定義HystrixCommand完成Hystrix的集成

其實現了run()方法執行核心業務,實現了getFallback()方法實現降級回退。通過之前Hystrix中Cache的應用,我們知道此處需要重寫getCacheKey()方法即可啓用Cache功能應用。
劇透:後續會專門寫一章來介紹如何實現Feign中的request cache應用,部分源碼參考如下:
  • CacheInvocationContextFactory :註冊了cache實現的相關action事件,如獲取cachekey方法等
  • CacheInvocationContext :將源方法參數進行了一定邏輯處理,便於後續cachekey生成時應用
  • HystrixCacheKeyGenerator :提供了cachekey的生成






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章