Spring Cloud Feign 自定義配置(重試、攔截與錯(cuò)誤碼處理) 代碼實(shí)踐
基于 spring-boot-starter-parent 2.1.9.RELEASE, spring-cloud-openfeign 2.1.3.RELEASE
引子
Feign 是一個(gè)聲明式、模板化的HTTP客戶端,簡(jiǎn)化了系統(tǒng)發(fā)起Http請(qǐng)求。創(chuàng)建它時(shí),只需要?jiǎng)?chuàng)建一個(gè)接口,然后加上FeignClient注解,使用它時(shí),就像調(diào)用本地方法一樣,作為開發(fā)者的我們完全感知不到這是在調(diào)用遠(yuǎn)程的方法,也感知不到背后發(fā)起了HTTP請(qǐng)求:
/** * @author axin * @suammry xx 客戶端 */@FeignClient(value = 'xxClient',url = '${xx.host:www.axin.com}')public interface DemoClient { @PostMapping(value = '/xxx/url', headers = 'Content-Type=application/json'}) yourResponse requestHTTP(@RequestBody JSONObject param);}
上述的代碼就是一個(gè)定義一個(gè)Feign HTTP 客戶端,在其他類中只需要 @Autowired DemoClient,就可以像調(diào)用本地方法一樣發(fā)起HTTP請(qǐng)求。
介紹就到這,接下來(lái)進(jìn)入主題,因?yàn)?FeignClient 將發(fā)起HTTP請(qǐng)求與解析返回報(bào)文都做了包裝,如果你的業(yè)務(wù)場(chǎng)景需要定制一些調(diào)用機(jī)制,比如:
我想在發(fā)起請(qǐng)求響應(yīng)超時(shí)失敗時(shí)自動(dòng)重試 —— 自定義重試機(jī)制 我想單獨(dú)對(duì)某些異常的HTTP狀態(tài)碼特殊處理 —— 自定義ErrorDecoder 服務(wù)端接口需要驗(yàn)證簽名,所以我方在發(fā)起請(qǐng)求時(shí)要生成簽名然后傳過(guò)去 —— 定義 Fegin 攔截器基于此,本文就以上述3個(gè)需求場(chǎng)景為例來(lái)介紹如何自定義 FeignClient 的配置
FeignClient的默認(rèn)配置類
Feign Client 默認(rèn)的配置類為 FeignClientsConfiguration, 這個(gè)類在 spring-cloud-netflix-core 的 jar 包下。
默認(rèn)注入了很多 Feign 相關(guān)的配置Bean,包括FeignRetryer、 FeignLoggerFactory 和 FormattingConversionService 等。另外,Decoder、Encoder和 Contract 這3個(gè)類在沒(méi)有Bean被注入的情況下,會(huì)自動(dòng)注入默認(rèn)配置的 Bean,即ResponseEntity Decoder、SpringEncoder 和 SpringMvcContract。
如果你不知道如何自己定義配置時(shí),不放點(diǎn)進(jìn)去看看人家默認(rèn)配置是如何實(shí)現(xiàn)的。這里就不曬源碼了。
FeignClient 注解參數(shù)
每個(gè)注解參數(shù)都做了注釋,詳情請(qǐng)見下方源碼:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface FeignClient {/** * 指定FeignClient的名稱,如果項(xiàng)目使用了Ribbon,name屬性會(huì)作為微服務(wù)的名稱,用于服務(wù)發(fā)現(xiàn) */@AliasFor('name')String value() default '';@DeprecatedString serviceId() default '';@AliasFor('value')String name() default '';/** * Sets the <code>@Qualifier</code> value for the feign client. * 這個(gè)bean在應(yīng)用上下文中的名字為接口的全限定名,你也可以使用 @FeignClient 注解中的 qualifier 屬性給bean指定一個(gè)別名 */String qualifier() default '';/** * url地址 */String url() default '';/** * 當(dāng)發(fā)生404錯(cuò)誤,如果該字段為true,會(huì)調(diào)用decoder進(jìn)行解碼,否則拋出FeignException */boolean decode404() default false;/** * 指定FeignClient 的配置類,優(yōu)先級(jí)最高,默認(rèn)的配置類為 FeignClientsConfiguration類 */Class<?>[] configuration() default {};/** * 配置熔斷器的處理類 */Class<?> fallback() default void.class;/** * 工廠類,用于生產(chǎn)fallback類示例,通過(guò)這個(gè)屬性我們可以實(shí)現(xiàn)每個(gè)接口通用的容錯(cuò)邏輯,減少重復(fù)代碼 */Class<?> fallbackFactory() default void.class;/** * 定義統(tǒng)一的路徑前綴 */String path() default '';/** * Whether to mark the feign proxy as a primary bean. Defaults to true. */boolean primary() default true;}
自定義Feign配置類
在 Spring Cloud 中,你可以通過(guò) @FeignClient 注解聲明額外的配置(比 FeignClientsConfiguration 級(jí)別高)去控制feign客戶端,以一開始的feign接口為例:
/** * @author axin * @suammry xx 客戶端 */@FeignClient(value = 'xxClient',url = '${xx.host:www.axin.com}',configuration = MyConfiguration.class)public interface DemoClient { @PostMapping(value = '/xxx/url', headers = 'Content-Type=application/json'}) yourResponse requestHTTP(@RequestBody JSONObject param);}
在上面這個(gè)示例中,feign客戶端在MyConfiguration中的配置將會(huì)覆蓋FeignClientsConfiguration中的配置。
要注意的是: MyConfiguration不需要使用@Configuration注解。如果加上了,它將全局生效。
Retryer-重試機(jī)制的自定義
/** * @author axin * @summary fegin 客戶端的自定義配置 */public class MyConfiguration { /** * 自定義重試機(jī)制 * @return */ @Bean public Retryer feignRetryer() { //fegin提供的默認(rèn)實(shí)現(xiàn),最大請(qǐng)求次數(shù)為5,初始間隔時(shí)間為100ms,下次間隔時(shí)間1.5倍遞增,重試間最大間隔時(shí)間為1s, return new Retryer.Default(); }}
ErrorDecoder-錯(cuò)誤解碼器的自定義
當(dāng)feign調(diào)用返回HTTP報(bào)文時(shí),會(huì)觸發(fā)這個(gè)方法,方法內(nèi)可以獲得HTTP狀態(tài)碼,可以用來(lái)定制一些處理邏輯等等。
/** * @author axin * @summary fegin 客戶端的自定義配置 */@Slf4jpublic class MyConfiguration { /** * 自定義重試機(jī)制 * @return */ @Bean public Retryer feignRetryer() { //最大請(qǐng)求次數(shù)為5,初始間隔時(shí)間為100ms,下次間隔時(shí)間1.5倍遞增,重試間最大間隔時(shí)間為1s, return new Retryer.Default(); } @Bean public ErrorDecoder feignError() { return (key, response) -> { if (response.status() == 400) {log.error('請(qǐng)求xxx服務(wù)400參數(shù)錯(cuò)誤,返回:{}', response.body()); } if (response.status() == 409) {log.error('請(qǐng)求xxx服務(wù)409異常,返回:{}', response.body()); } if (response.status() == 404) {log.error('請(qǐng)求xxx服務(wù)404異常,返回:{}', response.body()); } // 其他異常交給Default去解碼處理 // 這里使用單例即可,Default不用每次都去new return new ErrorDecoder.Default().decode(key, response); }; }}
采用了lambda的寫法,response變量是Response類型,通過(guò)status()方法可以拿到返回的HTTP狀態(tài)碼,body()可以獲得到響應(yīng)報(bào)文。
Feign攔截器實(shí)踐
攔截器在請(qǐng)求發(fā)出之前執(zhí)行,在攔截器代碼里可以修改請(qǐng)求參數(shù),header等等,如果你有簽名生成的需求,可以放在攔截器中來(lái)實(shí)現(xiàn)
/** * @author axin * @summary fegin 客戶端的自定義配置 */@Slf4jpublic class MyConfiguration { /** * 自定義重試機(jī)制 * @return */ @Bean public Retryer feignRetryer() { //最大請(qǐng)求次數(shù)為5,初始間隔時(shí)間為100ms,下次間隔時(shí)間1.5倍遞增,重試間最大間隔時(shí)間為1s, return new Retryer.Default(); } @Bean public ErrorDecoder feignError() { return (key, response) -> { if (response.status() == 400) {log.error('請(qǐng)求xxx服務(wù)400參數(shù)錯(cuò)誤,返回:{}', response.body()); } if (response.status() == 409) {log.error('請(qǐng)求xxx服務(wù)409異常,返回:{}', response.body()); } if (response.status() == 404) {log.error('請(qǐng)求xxx服務(wù)404異常,返回:{}', response.body()); } // 其他異常交給Default去解碼處理 // 這里使用單例即可,Default不用每次都去new return new ErrorDecoder.Default().decode(key, response); }; } /** * fegin 攔截器 * @return */ @Bean public RequestInterceptor cameraSign() { return template -> { // 如果是get請(qǐng)求 if (template.method().equals(Request.HttpMethod.GET.name())) {//獲取到get請(qǐng)求的參數(shù)Map<String, Collection<String>> queries = template.queries(); } //如果是Post請(qǐng)求 if (template.method().equals(Request.HttpMethod.POST.name())) {//獲得請(qǐng)求bodyString body = template.requestBody().asString();JSONPObject request = JSON.parseObject(body, JSONPObject.class); } //Do what you want... 例如生成接口簽名 String sign = '根據(jù)請(qǐng)求參數(shù)生成的簽名'; //放入url?之后 template.query('sign', sign); //放入請(qǐng)求body中 String newBody = '原有body' + sign; template.body(Request.Body.encoded(newBody.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8)); }; }}
可以看到代碼中給了如何獲取請(qǐng)求參數(shù)和修改請(qǐng)求參數(shù)的示例。
總結(jié)
小結(jié)一下,對(duì)于開頭提出的場(chǎng)景:
我想在發(fā)起請(qǐng)求響應(yīng)超時(shí)失敗時(shí)自動(dòng)重試 —— 自定義重試機(jī)制 我想單獨(dú)對(duì)某些異常的HTTP狀態(tài)碼特殊處理 —— 自定義ErrorDecoder 服務(wù)端接口需要驗(yàn)證簽名,所以我方在發(fā)起請(qǐng)求時(shí)要生成簽名然后傳過(guò)去 —— 定義 Fegin 攔截器給出了自定義 feign 配置的方式實(shí)現(xiàn)的樣例代碼,希望對(duì)你有用,如果有更好的方式簡(jiǎn)化HTTP請(qǐng)求,歡迎留言分享~
參考鏈接
重新定義Spring Cloud實(shí)戰(zhàn)
Spring Cloud Netflix官方文檔
總結(jié)
到此這篇關(guān)于Spring Cloud Feign 自定義配置(重試、攔截與錯(cuò)誤碼處理) 實(shí)踐的文章就介紹到這了,更多相關(guān)Spring Cloud Feign 自定義配置內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
1. Python的Tqdm模塊實(shí)現(xiàn)進(jìn)度條配置2. react axios 跨域訪問(wèn)一個(gè)或多個(gè)域名問(wèn)題3. WML語(yǔ)言的基本情況4. CSS代碼檢查工具stylelint的使用方法詳解5. 利用CSS制作3D動(dòng)畫6. Python 多線程之threading 模塊的使用7. 刪除docker里建立容器的操作方法8. .NET6打包部署到Windows Service的全過(guò)程9. ThinkPHP5 通過(guò)ajax插入圖片并實(shí)時(shí)顯示(完整代碼)10. Properties 持久的屬性集的實(shí)例詳解
