1.自定義feign元件
FeignClientProperties.FeignClientConfiguration
1.loggerLevel
預設使用的日志類 : Slf4jLogger
在發送和接收請求的時候,Feign定義了統一的日志門面來輸出日志資訊 , 并且将日志的輸出定義了四個等級:
級别 | 說明 |
---|---|
NONE | 不做任何記錄 |
BASIC | 隻記錄輸出Http 方法名稱、請求URL、傳回狀态碼和執行時間 |
HEADERS | 記錄輸出Http 方法名稱、請求URL、傳回狀态碼和執行時間 和 Header 資訊 |
FULL | 記錄Request 和Response的Header,Body和一些請求中繼資料 |
2.Retryer
預設重試類 : Retryer$
Feign 内置了一個重試器,當HTTP請求出現IO異常時,Feign會有一個最大嘗試次數發送請求,以下是Feign核心
重試參數 | 說明 | 預設值 |
---|---|---|
period | 初始重試時間間隔,當請求失敗後,重試器将會暫停 初始時間間隔(線程 sleep 的方式)後再開始,避免強刷請求,浪費性能 | 100ms |
maxPeriod | 當請求連續失敗時,重試的時間間隔将按照: 計算,按照等比例方式延長,但是最大間隔時間為 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);
}
比如,如果希望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
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.配置檔案配置
對應類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);
}
}