Spring Cloud Feign 自定義配置(重試、攔截與錯誤碼處理) 實踐

Spring Cloud Feign 自定義配置(重試、攔截與錯誤碼處理) 實踐

本篇文章主要分享下 Feign 自定義配置的工程實踐,希望你們可以找到些有用的東西,文章已被公衆號收錄

基於 spring-boot-starter-parent 2.1.9.RELEASE, spring-cloud-openfeign 2.1.3.RELEASE

引子

Feign 是一個聲明式、模板化的HTTP客戶端,簡化了系統發起Http請求。創建它時,只需要創建一個接口,然後加上FeignClient註解,使用它時,就像調用本地方法一樣,作爲開發者的我們完全感知不到這是在調用遠程的方法,也感知不到背後發起了HTTP請求:

/**
 * @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);

}

上述的代碼就是一個定義一個Feign HTTP 客戶端,在其他類中只需要 @Autowired DemoClient,就可以像調用本地方法一樣發起HTTP請求。

介紹就到這,接下來進入主題,因爲 FeignClient 將發起HTTP請求與解析返回報文都做了包裝,如果你的業務場景需要定製一些調用機制,比如:

  1. 我想在發起請求響應超時失敗時自動重試 —— 自定義重試機制
  2. 我想單獨對某些異常的HTTP狀態碼特殊處理 —— 自定義ErrorDecoder
  3. 服務端接口需要驗證簽名,所以我方在發起請求時要生成簽名然後傳過去 —— 定義 Fegin 攔截器

基於此,本文就以上述3個需求場景爲例來介紹如何自定義 FeignClient 的配置

FeignClient的默認配置類

Feign Client 默認的配置類爲 FeignClientsConfiguration, 這個類在 spring-cloud-netflix-core 的 jar 包下。

默認注入了很多 Feign 相關的配置Bean,包括FeignRetryer、 FeignLoggerFactory 和 FormattingConversionService 等。另外,Decoder、Encoder和 Contract 這3個類在沒有Bean被注入的情況下,會自動注入默認配置的 Bean,即ResponseEntity Decoder、SpringEncoder 和 SpringMvcContract。

如果你不知道如何自己定義配置時,不放點進去看看人家默認配置是如何實現的。這裏就不曬源碼了。

FeignClient 註解參數

每個註解參數都做了註釋,詳情請見下方源碼:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

	/**
	 * 指定FeignClient的名稱,如果項目使用了Ribbon,name屬性會作爲微服務的名稱,用於服務發現
	 */
	@AliasFor("name")
	String value() default "";
	@Deprecated
	String serviceId() default "";
	@AliasFor("value")
	String name() default "";
	
	/**
	 * Sets the <code>@Qualifier</code> value for the feign client.
	 * 這個bean在應用上下文中的名字爲接口的全限定名,你也可以使用 @FeignClient 註解中的 qualifier 屬性給bean指定一個別名
	 */
	String qualifier() default "";

	/**
	 * url地址
	 */
	String url() default "";

	/**
	 * 當發生404錯誤,如果該字段爲true,會調用decoder進行解碼,否則拋出FeignException
	 */
	boolean decode404() default false;

	/**
	 * 指定FeignClient 的配置類,優先級最高,默認的配置類爲 FeignClientsConfiguration類
	 */
	Class<?>[] configuration() default {};

	/**
	 * 配置熔斷器的處理類
	 */
	Class<?> fallback() default void.class;

	/**
	 * 工廠類,用於生產fallback類示例,通過這個屬性我們可以實現每個接口通用的容錯邏輯,減少重複代碼
	 */
	Class<?> fallbackFactory() default void.class;

	/**
	 * 定義統一的路徑前綴
	 */
	String path() default "";

	/**
	 * Whether to mark the feign proxy as a primary bean. Defaults to true.
	 */
	boolean primary() default true;
}

自定義Feign配置類

在 Spring Cloud 中,你可以通過 @FeignClient 註解聲明額外的配置(比 FeignClientsConfiguration 級別高)去控制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);
}

在上面這個示例中,feign客戶端在MyConfiguration中的配置將會覆蓋FeignClientsConfiguration中的配置。

要注意的是: MyConfiguration不需要使用@Configuration註解。如果加上了,它將全局生效

Retryer-重試機制的自定義

/**
 * @author axin
 * @summary fegin 客戶端的自定義配置
 */
public class MyConfiguration {

    /**
     * 自定義重試機制
     * @return
     */
    @Bean
    public Retryer feignRetryer() {
        //fegin提供的默認實現,最大請求次數爲5,初始間隔時間爲100ms,下次間隔時間1.5倍遞增,重試間最大間隔時間爲1s,
        return new Retryer.Default();
    }
}

ErrorDecoder-錯誤解碼器的自定義

當feign調用返回HTTP報文時,會觸發這個方法,方法內可以獲得HTTP狀態碼,可以用來定製一些處理邏輯等等。

/**
 * @author axin
 * @summary fegin 客戶端的自定義配置
 */
@Slf4j
public class MyConfiguration {

    /**
     * 自定義重試機制
     * @return
     */
    @Bean
    public Retryer feignRetryer() {
        //最大請求次數爲5,初始間隔時間爲100ms,下次間隔時間1.5倍遞增,重試間最大間隔時間爲1s,
        return new Retryer.Default();
    }

    @Bean
    public ErrorDecoder feignError() {
        return (key, response) -> {
            if (response.status() == 400) {
                log.error("請求xxx服務400參數錯誤,返回:{}", response.body());
            }

            if (response.status() == 409) {
                log.error("請求xxx服務409異常,返回:{}", response.body());
            }

            if (response.status() == 404) {
                log.error("請求xxx服務404異常,返回:{}", response.body());
            }

            // 其他異常交給Default去解碼處理
            // 這裏使用單例即可,Default不用每次都去new
            return new ErrorDecoder.Default().decode(key, response);
        };
    }

}

採用了lambda的寫法,response變量是Response類型,通過status()方法可以拿到返回的HTTP狀態碼,body()可以獲得到響應報文。

Feign攔截器實踐

攔截器在請求發出之前執行,在攔截器代碼裏可以修改請求參數,header等等,如果你有簽名生成的需求,可以放在攔截器中來實現

/**
 * @author axin
 * @summary fegin 客戶端的自定義配置
 */
@Slf4j
public class MyConfiguration {

    /**
     * 自定義重試機制
     * @return
     */
    @Bean
    public Retryer feignRetryer() {
        //最大請求次數爲5,初始間隔時間爲100ms,下次間隔時間1.5倍遞增,重試間最大間隔時間爲1s,
        return new Retryer.Default();
    }

    @Bean
    public ErrorDecoder feignError() {
        return (key, response) -> {
            if (response.status() == 400) {
                log.error("請求xxx服務400參數錯誤,返回:{}", response.body());
            }

            if (response.status() == 409) {
                log.error("請求xxx服務409異常,返回:{}", response.body());
            }

            if (response.status() == 404) {
                log.error("請求xxx服務404異常,返回:{}", response.body());
            }

            // 其他異常交給Default去解碼處理
            // 這裏使用單例即可,Default不用每次都去new
            return new ErrorDecoder.Default().decode(key, response);
        };
    }

     /**
     * fegin 攔截器
     * @return
     */
    @Bean
    public RequestInterceptor cameraSign() {
        return template -> {

            // 如果是get請求
            if (template.method().equals(Request.HttpMethod.GET.name())) {
                //獲取到get請求的參數
                Map<String, Collection<String>> queries = template.queries();
            }

            //如果是Post請求
            if (template.method().equals(Request.HttpMethod.POST.name())) {
                //獲得請求body
                String body = template.requestBody().asString();
                JSONPObject request = JSON.parseObject(body, JSONPObject.class);
            }

            //Do what you want... 例如生成接口簽名

            String sign = "根據請求參數生成的簽名";
            //放入url?之後
            template.query("sign", sign);

            //放入請求body中
            String newBody = "原有body" + sign;
            template.body(Request.Body.encoded(newBody.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8));
        };
    }
}

可以看到代碼中給瞭如何獲取請求參數和修改請求參數的示例。

總結

小結一下,對於開頭提出的場景:

  1. 我想在發起請求響應超時失敗時自動重試 —— 自定義重試機制
  2. 我想單獨對某些異常的HTTP狀態碼特殊處理 —— 自定義ErrorDecoder
  3. 服務端接口需要驗證簽名,所以我方在發起請求時要生成簽名然後傳過去 —— 定義 Fegin 攔截器

給出了自定義 feign 配置的方式實現的樣例代碼,希望對你有用,如果有更好的方式簡化HTTP請求,歡迎留言分享~

參考鏈接

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