天天看點

feign自定義元件(2)

1.自定義feign元件

FeignClientProperties.FeignClientConfiguration

feign自定義元件(2)

1.loggerLevel

預設使用的日志類 : Slf4jLogger

在發送和接收請求的時候,Feign定義了統一的日志門面來輸出日志資訊 , 并且将日志的輸出定義了四個等級:

級别 說明
NONE 不做任何記錄
BASIC 隻記錄輸出Http 方法名稱、請求URL、傳回狀态碼和執行時間
HEADERS 記錄輸出Http 方法名稱、請求URL、傳回狀态碼和執行時間 和 Header 資訊
FULL 記錄Request 和Response的Header,Body和一些請求中繼資料

2.Retryer

預設重試類 : Retryer$
feign自定義元件(2)

Feign 内置了一個重試器,當HTTP請求出現IO異常時,Feign會有一個最大嘗試次數發送請求,以下是Feign核心

重試參數 說明 預設值
period 初始重試時間間隔,當請求失敗後,重試器将會暫停 初始時間間隔(線程 sleep 的方式)後再開始,避免強刷請求,浪費性能 100ms
maxPeriod 當請求連續失敗時,重試的時間間隔将按照:

long interval = (long) (period * Math.pow(1.5, attempt - 1));

計算,按照等比例方式延長,但是最大間隔時間為 maxPeriod, 設定此值能夠避免 重試次數過多的情況下執行周期太長
1000ms
maxAttempts 最大重試次數 5

3.ErrorDecoder

預設異常解碼器類 : ErrorDecoder$Default
public static class Default implements ErrorDecoder {
    private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder();
    
    @Override
    public Exception decode(String methodKey, Response response) {
        FeignException exception = errorStatus(methodKey, response);
        Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
        if (retryAfter != null) {
            return new RetryableException(
                exception.getMessage(),
                response.request().httpMethod(),
                exception,
                retryAfter);
        }
        return exception;
    }

    private <T> T firstOrNull(Map<String, Collection<T>> map, String key) {
        if (map.containsKey(key) && !map.get(key).isEmpty()) {
            return map.get(key).iterator().next();
        }
        return null;
    }
}
           

4.RequestInterceptor

public interface RequestInterceptor {

  /**
   * 可以在構造RequestTemplate 請求時,增加或者修改Header, Method, Body 等資訊
   * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
   */
  void apply(RequestTemplate template);
}
           
feign自定義元件(2)

比如,如果希望Http消息傳遞過程中被壓縮,可以定義一個請求攔截器:

public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {

	/**
	 * Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.
	 * @param properties the encoding properties
	 */
	protected FeignAcceptGzipEncodingInterceptor(
			FeignClientEncodingProperties properties) {
		super(properties);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void apply(RequestTemplate template) {
		addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER,
				HttpEncoding.GZIP_ENCODING, HttpEncoding.DEFLATE_ENCODING);
	}
}
           
請求壓縮

Spring Cloud Feign支援對請求與響應進行GZIP壓縮,以減少通信過程中的性能損耗,隻需要下面兩個參數設定就可以開啟請求與響應的壓縮功能。

# 配置請求GZIP壓縮
feign.compression.request.enabled=true
# 配置響應GZIP壓縮
feign.compression.response.enabled=true
           

Spring Cloud Feign也對請求壓縮支援更細緻的設定。

# 配置請求GZIP壓縮
feign.compression.request.enabled=true
# 配置壓縮支援的MIME TYPE 預設值text/xml,application/xml,application/json
feign.compression.request.mime-types=text/xml,application/xml,application/json
# 配置壓縮資料大小的下限 預設值2048
feign.compression.request.min-request-size=2048

           

5.Decoder和Encoder

預設解碼器類 : OptionalDecoder
public interface Decoder {

  /**
   * 從Response 中提取Http消息正文,通過接口類聲明的傳回類型,消息自動裝配
   */
  Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;

  /** Default implementation of {@code Decoder}. */
  public class Default extends StringDecoder {

    @Override
    public Object decode(Response response, Type type) throws IOException {
      if (response.status() == 404)
        return Util.emptyValueOf(type);
      if (response.body() == null)
        return null;
      if (byte[].class.equals(type)) {
        return Util.toByteArray(response.body().asInputStream());
      }
      return super.decode(response, type);
    }
  }
}

           
預設編碼器類 : SpringEncoder
public interface Encoder {
  /** Type literal for {@code Map<String, ?>}, indicating the object to encode is a form. */
  Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD;

  /**
   * 将實體對象轉換成Http請求的消息正文中
   */
  void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;

  /**
   * Default implementation of {@code Encoder}.
   */
  class Default implements Encoder {

    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) {
      if (bodyType == String.class) {
        template.body(object.toString());
      } else if (bodyType == byte[].class) {
        template.body((byte[]) object, null);
      } else if (object != null) {
        throw new EncodeException(
            format("%s is not a type supported by this encoder.", object.getClass()));
      }
    }
  }
}

           

目前Feign 有以下實作:

Encoder/ Decoder 實作 說明
SpringEncoder,SpringDecoder 基于Spring MVC HttpMessageConverters 一套機制實作的轉換協定 ,應用于Spring Cloud 體系中
PageableSpringEncoder 提供對通過合成編碼spring Pageable的支援
OptionalDecoder 隻是在SpringDecoder上加了層判斷

6.Contract

定義注釋和值在接口上的有效性,并将值使用MethodMetadata封裝

預設解析類 : SpringMvcContract

feign自定義元件(2)
public interface Contract {

  /**
   * Called to parse the methods in the class that are linked to HTTP requests.
   * 傳入接口定義,解析成相應的方法内部中繼資料表示
   * @param targetType {@link feign.Target#type() type} of the Feign interface.
   */
  // TODO: break this and correct spelling at some point
  List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);
}

           

7.配置檔案配置

feign自定義元件(2)

對應類FeignClientProperties

@ConfigurationProperties("feign.client")
public class FeignClientProperties {
    
	private boolean defaultToProperties = true;

	private String defaultConfig = "default";

	private Map<String, FeignClientConfiguration> config = new HashMap<>();
    
    public static class FeignClientConfiguration {

		private Logger.Level loggerLevel;

		private Integer connectTimeout;

		private Integer readTimeout;

		private Class<Retryer> retryer;

		private Class<ErrorDecoder> errorDecoder;

		private List<Class<RequestInterceptor>> requestInterceptors;

		private Boolean decode404;

		private Class<Decoder> decoder;

		private Class<Encoder> encoder;

		private Class<Contract> contract;
    }
}

           

8.加載順序

加載順序可參考FeignClientFactoryBean#configureFeign()

protected void configureFeign(FeignContext context, Feign.Builder builder) {
		FeignClientProperties properties = this.applicationContext
				.getBean(FeignClientProperties.class);
		if (properties != null) {
             /**
              * 下面主要是設值的優先級
              */
			if (properties.isDefaultToProperties()) {
                 //先将子容器中存在bean設入builder中
				configureUsingConfiguration(context, builder);
                 //加載預設的屬性值替換剛剛設入builder的值
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
                 //加載對應contextId(這個就是@FeignClient裡面的contextId屬性)的配置替換剛剛設入builder的值
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
			}
			else {
                 //同上
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
				configureUsingConfiguration(context, builder);
			}
		}
		else {
			configureUsingConfiguration(context, builder);
		}
	}

           

繼續閱讀