Feign的構建過程及自定義擴展功能

spring-cloud-openfeign-core-2.1.1.RELEASE.jarHystrixFeign 的詳細構建過程:

@EnableFeignClients -> FeignClientsRegistrar 掃描 @Feign注解的類 -> FeignClientFactoryBean通過Targeter生產FeignClient -> Targeter通過Feign.Builder構建Feign -> Feign.Builder

1. 準備工作(配置)

  1. FeignAutoConfiguration自動配置類
    @Configuration@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")protected static class HystrixFeignTargeterConfiguration {@Bean@ConditionalOnMissingBeanpublic Targeter feignTargeter() {return new HystrixTargeter();}}@Configuration@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")protected static class DefaultFeignTargeterConfiguration {@Bean@ConditionalOnMissingBeanpublic Targeter feignTargeter() {return new DefaultTargeter();}}
  1. feign.hystrix.HystrixFeign類存在時,將 HystrixTargeter 注冊為 Targeter 類型的 bean
  2. feign.hystrix.HystrixFeign類不存在時,使用 DefaultTargeter
  3. 看起來似乎可以使用自定義的Targeter代替Hystrix或默認的,這樣就可以自定義各種功能了。實際上不行,因為 Targeterpackage 訪問級別的。

  4. FeignClientsConfiguration
@Configuration
public class FeignClientsConfiguration {@Bean@ConditionalOnMissingBeanpublic Retryer feignRetryer() {return Retryer.NEVER_RETRY;}@Bean@Scope("prototype")@ConditionalOnMissingBeanpublic Feign.Builder feignBuilder(Retryer retryer) {return Feign.builder().retryer(retryer);}@Configuration@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })protected static class HystrixFeignConfiguration {@Bean@Scope("prototype")@ConditionalOnMissingBean@ConditionalOnProperty(name = "feign.hystrix.enabled")public Feign.Builder feignHystrixBuilder() {return HystrixFeign.builder();}}
}

重要Feign 以及內部類 Feign.Builder 都是 public 訪問級別,可以注入自定義的bean。

2.EnableFeignClients與FeignClientsRegistrar類

將使用@FeignClient注解的類注冊成spring bean,并使用注解中的配置

  1. 在@EnableFeignClients注解中導入FeignClientsRegistrar類
  2. FeignClientsRegistrar類實現了ImportBeanDefinitionRegistrar類,會由spring框架執行實現方法 registerBeanDefinitions(AnnotationMetaData, BeanDefinitionRegistry)
  3. FeignClientsRegistrar中的 registerBeanDefinitions方法調用兩個方法
    1. registerDefaultConfiguration:注冊默認的配置
    2. registerFeignClients:注冊Feign客戶端(重點
  4. registerFeignClients:獲取 @EnableFeignClients注解中定義的配置掃描feign客戶端
  5. registerFeignClients:通過registerFeignClient(BeanDefinitionRegistry, AnnotationMetadata, Map)方法注冊每一個feignClient,過程:先獲取 @FeignClient注解中定義的配置,將配置應用在spring bean 工廠 FeignClientFactoryBean, 通過工廠類 FeignClientFactoryBean 為每一個使用@FeignClient注解的類生產 FeignClient,詳細過程見下一節

3.FeignClientFactoryBean

FeignClient工廠bean。

class FeignClientFactoryBeanimplements FactoryBean<Object>, InitializingBean, ApplicationContextAware{//...
}

通過實現方法 FactoryBean#getObject()來由spring框架生產FeignClient。

@Override
public Object getObject() throws Exception {return getTarget();
}/*** 獲得目標* 1. 獲得FeignContext* 2. 從FeignContext中獲得Feign構建器Feign.Builder* 3. 從FeignContext中獲得Client,判斷是否進行負載均衡* 4. 從FeignContext中獲得Target,并執行Target的默認方法target(FeignClientFactoryBean, Feign.Builder,FeignContext, Target.HardCodedTarget<T>);* 5.由于一開始注入的Feign.Builder是HystrixFeign.Builder,則此處是調用HystrixFeign.Builder里的對應方法*/
<T> T getTarget() {FeignContext context = this.applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);//省略部分代碼// ......Client client = getOptional(context, Client.class);if (client != null) {if (client instanceof LoadBalancerFeignClient) {// not load balancing because we have a url,// but ribbon is on the classpath, so unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}builder.client(client);}Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context,new HardCodedTarget<>(this.type, this.name, url));
}protected Feign.Builder feign(FeignContext context) {FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);Logger logger = loggerFactory.create(this.type);// @formatter:offFeign.Builder builder = get(context, Feign.Builder.class)// required values.logger(logger).encoder(get(context, Encoder.class)).decoder(get(context, Decoder.class)).contract(get(context, Contract.class));// @formatter:onconfigureFeign(context, builder);return builder;}

工廠獲得對象(目標):

1. 獲得FeignContext(feign上下文)
2. 從FeignContext中獲得Feign構建器Feign.Builder(public,可以在此使用自定義構建器)
3. 從FeignContext中獲得Client,判斷是否進行負載均衡
4. 從FeignContext中獲得Target,并執行Target的默認方法target(FeignClientFactoryBean, Feign.Builder,FeignContext, Target.HardCodedTarget<T>);
5. 由于一開始注入的 *Targeter* 是 *HystrixTargeter* ,則此處是調用 HystrixTargeter 里的對應方法(從第一節的配置來看,只要 *feign.hystrix.HystrixFeign* 類存在,就是注入的 *HystrixTargeter *, 否則是 *DefaultTargeter*,對于需要**自定義構建feign的,這里不太重要**)

4.Targeter

4.1.HystrixTargeter

class HystrixTargeter implements Targeter {@Overridepublic <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,FeignContext context, Target.HardCodedTarget<T> target) {// 若不是 HystrixFeign,則執行其對應的默認target方法。// 此處只處理HystrixFeign。if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {return feign.target(target);}feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;SetterFactory setterFactory = getOptional(factory.getName(), context,SetterFactory.class);if (setterFactory != null) {builder.setterFactory(setterFactory);}Class<?> fallback = factory.getFallback();if (fallback != void.class) {return targetWithFallback(factory.getName(), context, target, builder,fallback);}Class<?> fallbackFactory = factory.getFallbackFactory();if (fallbackFactory != void.class) {return targetWithFallbackFactory(factory.getName(), context, target, builder,fallbackFactory);}// 調用從Feign.Builder繼承的方法。return feign.target(target);}private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,Target.HardCodedTarget<T> target, HystrixFeign.Builder builder,Class<?> fallbackFactoryClass) {FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass,FallbackFactory.class);return builder.target(target, fallbackFactory);}private <T> T targetWithFallback(String feignClientName, FeignContext context,Target.HardCodedTarget<T> target, HystrixFeign.Builder builder,Class<?> fallback) {T fallbackInstance = getFromContext("fallback", feignClientName, context,fallback, target.type());return builder.target(target, fallbackInstance);}//...
}
  1. HystrixTarget只處理 Feign.Builder 類型為 feign.hystrix.HystrixFeign.Builder
  2. 若feign構建器不是 feign.hystrix.HystrixFeign.Builder 類型,則執行注入的 feign 構建器的默認target方法
  3. 因此,即使注入的 Targeter 是 HystrixTargeter,此處也可以執行自定義 Feign.Builder
  4. 理解:Feign.Builder#target(Target) 方法通常不會被 override(后續會講解為什么不重寫此方法)

4.2.DefaultTargeter

class DefaultTargeter implements Targeter {@Overridepublic <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,FeignContext context, Target.HardCodedTarget<T> target) {return feign.target(target);}
}
  1. 執行 Feign.Builder (子)類型對應的 默認 target方法。
  2. 理解:Feign.Builder#target(Target) 方法通常不會被 override(后續會講解為什么不重寫此方法)

5.FeignBuilder

feign構建器:構建feign對象。

Feign的目的是將 http api 包裝成 restful 風格以便開發。

在實現中,Feign 是一個為目標http apis 生成 feign對象(Feign#newInstance)的工廠。

上述步驟目前需要的都是通過對應的 Builder 構建對應的Feign。

public abstract class Feign {public static Builder builder() {return new Builder();}public abstract <T> T newInstance(Target<T> target);public static class Builder {public <T> T target(Target<T> target) {return build().newInstance(target);}public Feign build() {SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy);ParseHandlersByName handlersByName =new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,errorDecoder, synchronousMethodHandlerFactory);return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);}}
}
  1. Feign.Builder#target(Target) 方法里面實際上調用的是 build() 方法來構建對象,因此重寫 build() 方法即可,沒有必要還重寫 target(Target) 方法
  2. Feign 以及內部類 Feign.Builder 都是 public可以重寫并注入自定義的bean

5.1.HystrixFeign

public final class HystrixFeign {public static final class Builder extends Feign.Builder {  @Overridepublic Feign build() {return build(null);}// 提供一系列的target方法,支持各種配置:fallback、FallBackFactory等public <T> T target(Target<T> target, T fallback) {return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null).newInstance(target);}public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {return build(fallbackFactory).newInstance(target);}public <T> T target(Class<T> apiType, String url, T fallback) {return target(new Target.HardCodedTarget<T>(apiType, url), fallback);}public <T> T target(Class<T> apiType,String url,FallbackFactory<? extends T> fallbackFactory) {return target(new Target.HardCodedTarget<T>(apiType, url), fallbackFactory);}/** Configures components needed for hystrix integration. */Feign build(final FallbackFactory<?> nullableFallbackFactory) {super.invocationHandlerFactory(new InvocationHandlerFactory() {@Overridepublic InvocationHandler create(Target target,Map<Method, MethodHandler> dispatch) {return new HystrixInvocationHandler(target, dispatch, setterFactory,nullableFallbackFactory);}});super.contract(new HystrixDelegatingContract(contract));return super.build();}

基本到了這一步,需要設置的東西,都可以配置了。

  1. 雖然 build 方法中涉及到 InvocationHandler,但基本不需要改什么,而 InvocationHandler 竟然也是 package 訪問級別,所以只好復制一個,使用自己的。
  2. HystrixDelegatingContract 是 public 級別,不需要修改的話,仍然用這個。

5.2示例

以下示例參考 SentinelFeign

@Override
public Feign build() {super.invocationHandlerFactory(new InvocationHandlerFactory() {@Overridepublic InvocationHandler create(Target target,Map<Method, MethodHandler> dispatch) {// using reflect get fallback and fallbackFactory properties from// FeignClientFactoryBean because FeignClientFactoryBean is a package// level class, we can not use it in our packageObject feignClientFactoryBean = Builder.this.applicationContext.getBean("&" + target.type().getName());Class fallback = (Class) getFieldValue(feignClientFactoryBean,"fallback");Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean,"fallbackFactory");String name = (String) getFieldValue(feignClientFactoryBean, "name");Object fallbackInstance;FallbackFactory fallbackFactoryInstance;// check fallback and fallbackFactory properties// 以下邏輯在HystrixTargeter中有,但執行自定義的builder,不會執行到那段邏輯,因此此處加上。if (void.class != fallback) {fallbackInstance = getFromContext(name, "fallback", fallback,target.type());return new PegasusInvocationHandler(target, dispatch, setterFactory,new FallbackFactory.Default(fallbackInstance));}if (void.class != fallbackFactory) {fallbackFactoryInstance = (FallbackFactory) getFromContext(name,"fallbackFactory", fallbackFactory,FallbackFactory.class);return new PegasusInvocationHandler(target, dispatch, setterFactory,fallbackFactoryInstance);}// 此處還是會使用一個默認的FallbackFactory。return new PegasusInvocationHandler(target, dispatch, setterFactory, new PegasusFeignFallbackFactory<>(target));}private Object getFromContext(String name, String type,Class fallbackType, Class targetType) {Object fallbackInstance = feignContext.getInstance(name,fallbackType);if (fallbackInstance == null) {throw new IllegalStateException(String.format("No %s instance of type %s found for feign client %s",type, fallbackType, name));}if (!targetType.isAssignableFrom(fallbackType)) {throw new IllegalStateException(String.format("Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",type, fallbackType, targetType, name));}return fallbackInstance;}});super.contract(new HystrixDelegatingContract(contract));return super.build();
}

需要自定義fallbackFactory,則實現 feign.hystrix.FallbackFactory類,需要自定義fallback,則實現 org.springframework.cglib.proxy.MethodInterceptor即可

6.總結

  1. 由于Feign構建過程所用到的 Targeterpackage 訪問級別的,不能使用自定義的
  2. Feign以及Feign.Builderpublilc,給了我們擴展的空間。

7.參考資料

  1. feign-hystrix-10.1.0.jarspring-cloud-openfeign-core-2.1.1.RELEASE.jar
  2. spring-cloud-alibaba-sentinel-0.9.0.RELEASE.jar中的 sentinelFeign 實現
  3. Spring Cloud Alibaba Sentinel 整合 Feign 的設計實現

轉載于:https://blog.51cto.com/14355901/2399829

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/275070.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/275070.shtml
英文地址,請注明出處:http://en.pswp.cn/news/275070.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

angelica類似_親愛的當歸(Angelica)是第一個讓我哭泣的VR體驗

angelica類似It was a night just like any other night. I finished work for the day and closed my laptop. I had dinner and after an hour, I put on my Oculus Quest headset in order to begin my VR workout.就像其他任何夜晚一樣&#xff0c; 這 是一個夜晚。 我完成…

面試官:請手寫一個帶取消功能的延遲函數,axios 取消功能的原理是什么

大家好&#xff0c;我是若川。最近組織了源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。本文倉庫 https://githu…

關于base64編碼的原理及實現

我們的圖片大部分都是可以轉換成base64編碼的data&#xff1a;image。 這個在將canvas保存為img的時候尤其有用。雖然除ie外&#xff0c;大部分現代瀏覽器都已經支持原生的基于base64的encode和decode&#xff0c;例如btoa和atob。&#xff08;將canvas畫布保存成img并強制改變…

Django web開發系列(五)模板

一 前言在上一節了解到視圖函數處理后&#xff0c;會將結果渲染到創建的html頁面&#xff0c;但html如何接收并顯示視圖函數返回的動態數據呢&#xff1f;最常用的做法就是使用模板(Template)&#xff0c;本節將簡單介紹一下模板的作用和用法。 可以這樣簡單的理解模板的概念&a…

facebook 面試_如何為您的Facebook產品設計面試做準備

facebook 面試重點 (Top highlight)Last month, I joined Facebook to work on Instagram DMs and as a way to pay it forward, I 上個月&#xff0c;我加入了Facebook&#xff0c;從事Instagram DM的工作&#xff0c;作為一種支付方式&#xff0c;我 offered to help anyone…

8年了,開始寫點東西了

大家好&#xff0c;我是若川。最近組織了源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。今天分享一位大佬的文章…

荒徑 弗羅斯特_弗羅斯特龐克,顛覆性城市建設者

荒徑 弗羅斯特Most gamers are familiar with Will Wright’s famous SimCity series. It created the city building genre and there have been many attempts over the years to ape it. But few developers have been bold enough to completely deconstruct the formula; …

2012年1月份第2周51Aspx源碼發布詳情

WP7手指畫圖應用源碼 2012-01-14 [VS2010] 游戲介紹&#xff1a;Windows Phone 7手指畫圖應用 – FingerPaint&#xff0c;您通過此游戲可以隨心畫一些感興趣的東西&#xff0c;陶冶情操。操作簡單&#xff0c;頁面簡潔。適合新手學習參考。 WP7 Car Bloke(交通工具開銷記錄)源…

Gitee 如何自動部署博客 Pages?推薦用這個GitHub Actions!

大家好&#xff0c;我是若川。最近組織了源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。前段時間我把自己的博客…

Java io流學習總結(三)

轉載于&#xff1a;https://www.cnblogs.com/ll409546297/p/7197911.html java.io幾種讀寫文件的方式 一、Java把這些不同來源和目標的數據都統一抽象為數據流。 Java語言的輸入輸出功能是十分強大而靈活的。 在Java類庫中&#xff0c;IO部分的內容是很龐大的&#xff0c;因為它…

現在流行的畫原型圖工具_原型資源圖:8種流行原型工具的綜合指南

現在流行的畫原型圖工具Although tools are not the most important things to learn as a UX designer, inevitably you need to use it in order to achieve your more important goals, to solve user’s problems. This article covers today’s 8 popular UX prototyping …

持續5個月,200+筆記,3千多人參與,邀請你來學源碼~

注意&#xff1a;本文點擊文末閱讀原文可查看文中所有鏈接。我正在參加掘金年度人氣作者投票活動&#xff0c;大家有空可以加微信群幫忙投票&#xff0c;感謝大家&#xff01;想起今天還沒發文&#xff0c;就開放下微信群二維碼&#xff0c;大家掃碼進群讀源碼和幫忙投票吧。群…

自己動手開發調試器 01

背景: 在做XXX編譯器檢證時經常需要區分是代碼端錯誤&#xff0c;還是編譯器端錯誤&#xff0c;因此對代碼進行調試是必不可少的。但是狗日的甲方并沒有提供對應的調試器XXXDB&#xff0c;而用GDB調試XXX生成的可執行程序很不穩定&#xff0c;經常出現異常&#xff0c;干脆…

02如何抓住重點,系統高效地學習數據結構與算法?

以下內容總結自極客時間王爭大佬的《數據結構與算法之美》課程&#xff0c;本文章僅供個人學習總結。 什么是數據結構?什么是算法? 從廣義上講&#xff0c;數據結構就是指一組數據的存儲結構。算法就是操作數據的一組方法。 類比圖書館的書籍&#xff0c;我們如果想找一本書可…

第2年,倒數第3天,1.5萬票,感動!

1源碼共讀大家好&#xff0c;我是若川。眾所周知。從8月份開始&#xff0c;我組織了源碼共讀活動&#xff0c;至今已經有5個月了&#xff0c;每周一期&#xff0c;進行到了第18期。每周堅持寫源碼解讀文章&#xff0c;每天堅持答疑解惑&#xff0c;幫助了很多人學會看源碼&…

啟發式搜索給神經網絡_神經科學如何支持UX啟發式

啟發式搜索給神經網絡重點 (Top highlight)Interaction and UX designers have long known and used heuristics to guide the creation of a user-friendly interface. We know empirically that these principles work, and they make “common sense”. These heuristics th…

Django實戰(1):需求分析和設計

Depot是《Agile Web Development with Rails》中的一個購物車應用。 該書中用多次迭代的方法&#xff0c;逐步實現購物車應用&#xff0c;使很多人走上了rails開發的道路。 遺憾的是Django世界中好像沒有類似的指引&#xff0c;也許是因為pythoner 不需要具體的例子。 但是如果…

使用 apiDoc 為你的Node.js API 生成文檔

翻譯&#xff1a; 瘋狂的技術宅 原文&#xff1a;jonathas.com/documenting… 未經許可&#xff0c;禁止轉載&#xff01; 當你為其他開發人員&#xff08;前端&#xff0c;桌面&#xff0c;移動等&#xff09;開發 API 時&#xff0c;需要生成一份風格良好的文檔&#xff0c;以…

海浪 shader_海浪下的發現

海浪 shaderI’ve been playing Subnautica for over 25 hours now, and likely have at least that many more to go. The game puts you in the shoes of a crew member on the Aurora, a spaceship that suffers a catastrophic incident and plummets to the largely ocean…

最后一天,特邀小姐姐配音拉票,今日可投28票

1源碼共讀大家好&#xff0c;我是若川。最后一天&#xff0c;特邀小姐姐配音拉票&#xff0c;超級好聽。眾所周知。從8月份開始&#xff0c;我組織了源碼共讀活動&#xff0c;至今已經有5個月了&#xff0c;每周一期&#xff0c;進行到了第18期。每周堅持寫源碼解讀文章&#x…