前言
feign发送完请求后, 拿到返回结果, 那么这个返回结果肯定是需要经过框架进一步处理然后再返回到调用者的, 其中ResponseHandler
就是用来处理这个返回结果的, 这也是符合正常思维的处理方式, 例如springmvc部分在调用在controller端点前后都会增加扩展点。
从图中可以看得feign的返回处理应该不会很复杂, 并且可以自定义日志对象,和日志级别,对返回值进行解码, 并允许我们使用责任链来处理返回结果。
代码解析
老规矩, 查看类结构, 看该对象给我们提供了哪些功能
ResponseHandler
java
public class ResponseHandler {
/**
* 日志等级; 默认是Logger.Level.NONE
*/
private final Level logLevel;
/**
* 日志对象; 默认是NoOpLogger
*/
private final Logger logger;
/**
* 对正常响应数据进行解码的解码器; 默认是Decoder.Default
*/
private final Decoder decoder;
/**
* 404异常时, 对错误信息进行解码的解码器, 默认是ErrorDecoder.Default
*/
private final ErrorDecoder errorDecoder;
/**
* 当返回类型不为void, 并且响应状态码是404 1.如果dismiss404为true时, 那么忽略异常 2.如果dismiss404为false,则会抛异常
*/
private final boolean dismiss404;
/**
* 是否在解码返回数据后关闭相应的流
*/
private final boolean closeAfterDecode;
/**
* 是否对void类型返回值进行解码
*/
private final boolean decodeVoid;
/**
* 响应拦截链
*/
private final ResponseInterceptor.Chain executionChain;
/**
* 唯一构造器
*/
public ResponseHandler(Level logLevel, Logger logger, Decoder decoder, ErrorDecoder errorDecoder,
boolean dismiss404, boolean closeAfterDecode, boolean decodeVoid,
ResponseInterceptor.Chain executionChain) {...}
public Object handleResponse(String configKey,
Response response,
Type returnType,
long elapsedTime)
throws Exception {...}
}
ResponseHandler
类是同步请求结果处理器
- 它提供了一个参数非常多且唯一一的构造器
- 提供了一个方法
handleResponse
来处理返回对象
其中有个属性是ResponseInterceptor.Chain
, 它是用来处理返回对象的责任链, 我们简单认识一下它
ResponseInterceptor.Chain
java
public interface ResponseInterceptor {
interface Chain {
Chain DEFAULT = InvocationContext::proceed;
Object next(InvocationContext context) throws Exception;
}
/**
* 拦截器套娃包装
*/
default ResponseInterceptor andThen(ResponseInterceptor nextInterceptor) {
return (ic, chain) -> intercept(ic,
nextContext -> nextInterceptor.intercept(nextContext, chain));
}
/**
* 执行拦截器
*/
Object intercept(InvocationContext invocationContext, Chain chain) throws Exception;
/**
* 执行责任链
*/
default Chain apply(Chain chain) {
return request -> intercept(request, chain);
}
}
它是内聚在ResponseInterceptor
中的一个接口。
- 定义了一个默认链
Chain.DEFAULT
- 提供了一个获取下一节点的方法
- ResponseInterceptor提供了一个静态的包装方法
andThen
, 用来拦截器套娃 - ResponseInterceptor提供了一个用于执行拦截器的方法
intercept
, - ResponseInterceptor提供了一个用于执行责任链的方法
apply
它在Feign.Builder
的父类BaseBuilder
中实例化
java
public abstract class BaseBuilder<B extends BaseBuilder<B, T>, T> implements Cloneable {
/**
* 返回结果拦截器
*/
protected final List<ResponseInterceptor> responseInterceptors = new ArrayList<>();
// 用新的拦截器替换掉原有的拦截器
public B responseInterceptors(Iterable<ResponseInterceptor> responseInterceptors) {
this.responseInterceptors.clear();
for (ResponseInterceptor responseInterceptor : responseInterceptors) {
this.responseInterceptors.add(responseInterceptor);
}
return thisB;
}
/**
* 添加单个拦截器
*/
public B responseInterceptor(ResponseInterceptor responseInterceptor) {
this.responseInterceptors.add(responseInterceptor);
return thisB;
}
/**
* response拦截器组成链条
*/
protected ResponseInterceptor.Chain responseInterceptorChain() {
ResponseInterceptor.Chain endOfChain =
ResponseInterceptor.Chain.DEFAULT;
ResponseInterceptor.Chain executionChain = this.responseInterceptors.stream()
.reduce(ResponseInterceptor::andThen)
.map(interceptor -> interceptor.apply(endOfChain))
.orElse(endOfChain);
return (ResponseInterceptor.Chain) Capability.enrich(executionChain,
ResponseInterceptor.Chain.class, capabilities);
}
}
该方法responseInterceptorChain
是protected修饰的
, 子类可以重写它。允许我们每次添加一个拦截器, 或者直接全部替换。
构建责任链
java
ResponseInterceptor.Chain executionChain = this.responseInterceptors.stream()
.reduce(ResponseInterceptor::andThen)
.map(interceptor -> interceptor.apply(endOfChain))
.orElse(endOfChain);
这和我们传统看到的责任链有点不同, 传统的责任链一般是有前后节点以及上下文, 然后用责任链触发调用, 这里的区别在于责任链中节点的构建方式有点不同, 这里是嵌套包装的性质.
为了让大家更好的理解这坨代码, 我把它平铺开, 写段伪代码
java
public ResponseInterceptor.Chain buildRespChain() {
ResponseInterceptor.Chain endOfChain = ResponseInterceptor.Chain.DEFAULT;
// 合并所有拦截器成一个 ResponseInterceptor
ResponseInterceptor combinedInterceptor = null;
for (ResponseInterceptor interceptor : this.responseInterceptors) {
if (combinedInterceptor == null) {
combinedInterceptor = interceptor;
} else {
ResponseInterceptor previousCombinedInterceptor = combinedInterceptor;
combinedInterceptor = new ResponseInterceptor() {
@Override
public Object intercept(InvocationContext ic, Chain chain) throws Exception {
return previousCombinedInterceptor.intercept(ic, new Chain() {
@Override
public Object next(InvocationContext context) throws Exception {
return interceptor.intercept(context, chain);
}
});
}
};
}
}
// 如果没有拦截器,直接返回 endOfChain
if (combinedInterceptor == null) {
return endOfChain;
}
ResponseInterceptor temp = combinedInterceptor;
// 使用 apply 构造最终责任链
return new ResponseInterceptor.Chain() {
@Override
public Object next(InvocationContext request) throws Exception {
return temp.intercept(request, endOfChain);
}
};
}
-
核心就是调用每一个过滤节点(这里是拦截器)的时候把下一个传节点封装成Chain传进去, 然后我们就可以在拦截器的
intercept
方法中通过Chain的next方法调用下一个节点拦截器了 -
最后再构建一个最终的
Chain
, 在next
方法中调用构建出来的拦截器链, 并传入默认节点endOfChain
, 也就是说我们自定义的拦截器会先执行 -
最后再执行这个
endOfChain
(取决于各拦截器执行的行为, 可以决定是正序还是倒序), 返回整条连的执行结果
说到责任链, 我的这篇文章也介绍了一个我之前写的拦截器项目通用责任链在项目中使用
那么这个默认的的节点endOfChain
长什么样呢? 我们得研究一下
InvocationContext
它是用lambda表达式表示的一个Chain, 定义在响应链处理器的最后一个节点, 用来处理最终的返回结果
关于lambda表达式, 如果大家不是很了解, 可以去看看我的这篇文章 lambda表达式原理
Chain DEFAULT = InvocationContext::proceed;
java
public class InvocationContext {
public Object proceed() throws Exception {
// 方法返回类型是Response(一般也不会是这个)
if (returnType == Response.class) {
// 读取流中的数据; 1.如果响应体为空或者大于8k, 直接返回response 2.响应数据不为空且小于8k, 将数据流取出来
return disconnectResponseBodyIfNeeded(response);
}
try {
// 1.响应正常 或者 2.响应状态码是404 并且 dismiss404为true 并且 返回值类型不是void类型
final boolean shouldDecodeResponseBody =
(response.status() >= 200 && response.status() < 300)
|| (response.status() == 404 && dismiss404
&& !isVoidType(returnType));
// shouldDecodeResponseBody为false的几种情况如下
// 1.状态码不是200-300 并且 2.状态码不是404; 例如status为500
// 2.状态码不是200-300 并且 状态码是404且dismiss404为false
// 3.状态码不是200-300 并且 状态码是404且dismiss404为true 并且 返回类型是void类型
if (!shouldDecodeResponseBody) {
// 抛异常, 这里可能是重试异常RetryableException
throw decodeError(configKey, response);
}
// 1.返回值类型是void 2.不允许对void类型进行解码
if (isVoidType(returnType) && !decodeVoid) {
// 关闭流
ensureClosed(response.body());
return null;
}
// 获取返回值的原始类型
Class<?> rawType = Types.getRawType(returnType);
// 返回类型是TypedResponse类型; 类似于泛型中的 <rawType extend TypedResponse>
if (TypedResponse.class.isAssignableFrom(rawType)) {
// 获取TypedResponse中参数泛型的类型, 也就是TypedResponse<T>中的T
Type bodyType = Types.resolveLastTypeParameter(returnType, TypedResponse.class);
// 这里把response解码成TypedResponse<T>中的T类型然后设置给body
return TypedResponse.builder(response)
.body(decode(response, bodyType))
.build();
}
// 把response解码成bodyType类型
return decode(response, returnType);
} finally {
// decode之后关闭流
if (closeAfterDecode) {
ensureClosed(response.body());
}
}
}
}
private static Response disconnectResponseBodyIfNeeded(Response response) throws IOException {
// 如果数据小于8k, 证明数据已经返回完了, 不需要再读取数据; 否则返回response本身继续读取数据
final boolean shouldDisconnectResponseBody = response.body() != null
&& response.body().length() != null
&& response.body().length() <= MAX_RESPONSE_BUFFER_SIZE;
// 1.如果响应体为空或者大于8k, 直接返回response
if (!shouldDisconnectResponseBody) {
return response;
}
try {
// 响应数据不为空且小于8k, 将数据流取出来
final byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
} finally {
// 关闭响应流(inputStream)
ensureClosed(response.body());
}
}
小结一下
- 如果返回类型是
Response
类型
- 如果返回数据小于8k, 证明数据已经返回完了, 不需要再读取数据;
- 否则返回response本身继续读取数据, 并关闭响应流数据
- 如果请求失败, 如下情况将会抛异常(可能是重试异常)
- 状态码不是200-300 并且 2.状态码不是404; 例如status为500
- 状态码不是200-300 并且 状态码是404且dismiss404为false
- 状态码不是200-300 并且 状态码是404且dismiss404为true 并且 返回类型是void类型
- 当方法返回值不是空时, 如果不想404报错, dismiss404参数设置为true就行
- 如果返回类型是void, 并且不允许对void类型进行解码, 直接关闭流
- 如果返回值类型是
TypedResponse
, 那么对返回数据的body解码(这里只支持String和byte[]类型) - 如果返回类型既不是
Response
, 也不是TypedResponse
, 直接将返回的响应体数据解码成方法返回类型(这里只支持String和byte[]类型)
这里正常数据解码器是
Decoder.Default
, 异常数据解码器是ErrorDecoder.Default
泛型的解析工具类是
Types
, 如果想要更多的了解泛型, 可以看我的这篇文章 java泛型探究
这里了解一下Response
和TypedResponse
的区别
java
public final class Response implements Closeable {
// ...忽略其它属性
private final Body body;
}
public final class TypedResponse<T> {
// ...忽略其它属性
private final T body;
public Builder body(T body) {
this.body = body;
return this;
}
}
- Response实现了
Closeable
接口, 可以用来自动关闭流 - Response的响应体对象是一个Body类型, 而
TypedResponse
的响应体是一个泛型, 该泛型是body进行解码转换的结果
关于异常状态码的响应的重试处理
java
// shouldDecodeResponseBody为false的几种情况如下
// 1.状态码不是200-300 并且 2.状态码不是404; 例如status为500
// 2.状态码不是200-300 并且 状态码是404且dismiss404为false
// 3.状态码不是200-300 并且 状态码是404且dismiss404为true 并且 返回类型是void类型
if (!shouldDecodeResponseBody) {
// 抛异常
throw decodeError(configKey, response);
}
private Exception decodeError(String methodKey, Response response) {
try {
// 默认是ErrorDecoder.Default; 如果有重试, 会抛RetryableException
return errorDecoder.decode(methodKey, response);
} finally {
// 关闭响应流(inputStream)
ensureClosed(response.body());
}
}
这里errorDecoder
默认是ErrorDecoder.Default
java
public class Default implements ErrorDecoder {
public Exception decode(String methodKey, Response response) {
FeignException exception = errorStatus(methodKey, response, maxBodyBytesLength,
maxBodyCharsLength);
// 重试的时间 毫秒类型; RETRY_AFTER:Retry-After
Long retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
if (retryAfter != null) {
return new RetryableException(
response.status(),
exception.getMessage(),
response.request().httpMethod(),
exception,
retryAfter,
response.request());
}
return exception;
}
}
-
这里
retryAfterDecoder
默认是ErrorDecoder.RetryAfterDecoder
, 定义在RetryAfterDecoder
内部. -
firstOrNull(response.headers(), RETRY_AFTER)
用与获取响应头中Retry-After
属性的值(毫秒) -
如果返回了正确的重试时间, 那么抛
RetryableException
异常, 否则抛FeignException
异常
java
static class RetryAfterDecoder {
public Long apply(String retryAfter) {
if (retryAfter == null) {
return null;
}
// 也就是数字 或者数字.?0*, 例如 匹配:100、100.、100.0、100.00
if (retryAfter.matches("^[0-9]+\\.?0*$")) {
// 去掉小数部分, 例如 100.00 -> 100
retryAfter = retryAfter.replaceAll("\\.0*$", "");
// 转毫秒
long deltaMillis = SECONDS.toMillis(Long.parseLong(retryAfter));
// 下次重试时间
return currentTimeMillis() + deltaMillis;
}
try {
// 否则就是时间格式
return ZonedDateTime.parse(retryAfter, dateTimeFormatter).toInstant().toEpochMilli();
} catch (NullPointerException | DateTimeParseException ignored) {
// 其它格式不重试
return null;
}
}
}
- 响应头
Retry-After
属性为空或者对应的值为空, 返回null - 如果``Retry-After
属性返回的是数字, 那么计算下次重试的时间点(当前时间+响应头
Retry-After`设置的时间) - 如果``Retry-After
属性返回的是时间格式, 那么它只能是
Day-of-Week, DD Month YYYY HH:mm:ss GMT例如
Tue, 3 Jun 2008 11:05:30 GMT`这种
响应责任链的部分介绍完了, 下面回归到ResponseHandler
类
ResponseHandler详情
ResponseHandler
java
public Object handleResponse(String configKey,
Response response,
Type returnType,
long elapsedTime)
throws Exception {
try {
// 打印响应相关的日志
response = logAndRebufferResponseIfNeeded(configKey, response, elapsedTime);
// 执行责任链, 处理响应数据
return executionChain.next(
new InvocationContext(configKey, decoder, errorDecoder, dismiss404, closeAfterDecode,
decodeVoid, response, returnType));
} catch (final IOException e) {
// 打印日常日志
if (logLevel != Level.NONE) {
logger.logIOException(configKey, logLevel, e, elapsedTime);
}
// 抛FeignException异常
throw errorReading(response.request(), response, e);
} catch (Exception e) {
// 关闭响应流
ensureClosed(response.body());
throw e;
}
}
小结一下
- 打印相应结果相关的日志
- 使用响应责任链处理返回结果
- 异常情况打印日志然后包装成FeignException异常异常抛出
- 最后关闭响应流
logAndRebufferResponseIfNeeded
在执行相应责任链调用之前, 先打印了一段日志
java
private Response logAndRebufferResponseIfNeeded(String configKey,
Response response,
long elapsedTime)
throws IOException {
// 默认是NoOpLogger, 也就是不打印日志
if (logLevel == Level.NONE) {
return response;
}
return logger.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
}
- 默认的
logLevel
是Level.NONE
, 也就是不打印日志 - 以Slf4j举例, Slf4j在feign中是以
Slf4jLogger
对象存在, 它包装了org.slf4j.Logger对象
Slf4jLogger#logAndRebufferResponse
java
@Override
protected Response logAndRebufferResponse(String configKey,
Level logLevel,
Response response,
long elapsedTime)
throws IOException {
if (logger.isDebugEnabled()) {
// 这里调用feign.Logger中的方法
return super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
}
return response;
}
举例
java
@Test
void logFunc() {
DemoClient client = Feign.builder()
.logLevel(feign.Logger.Level.FULL)
.logger(new Slf4jLogger())
.dismiss404()
.target(DemoClient.class, "http://localhost:8080");
client.getDemo1("uncleqiao");
}
这里将打印请求和响应的所以信息, 包括响应体; 这里dismiss404是为了测试时候, 接口不存在时的404不报错
总结
- Feign通过
ResponseHandler
来处理响应的结果 ResponseHandler
主要通过响应责任链来处理响应结果, 我们可以自定义其中的节点拦截器做自定义的事情- 责任链中默认添加了一个节点
InvocationContext
, 用来真正处理返回结果
- 如果方法返回类型是
Response
, 那么根据返回的数据长度是否为空或者大于8k, 如果满足, 直接返回Response
, 如果小于8k, 重新构建新的Response
返回, 并关闭响应流; 这里重新构建是因为流只能读取一次, 如果关闭了就读取不到了
- 如果状态码是404, 并且设置dismiss404为true, 那么将忽略异常
- 如果状态码不是200-300也不是404, 那么根据响应头是否鞋带
Retry-After
, 并有正确的值, 那么将会抛重试异常进行重试, 否则抛FeignException
, 然后关闭响应流 - 如果返回类型是void, 并且不允许对void进行编码(decodeVoid=false), 那么关闭响应流, 直接返回null
- 如果方法返回值是
TypedResponse
类型, 那么将响应体通过Decoder.Default
解码转为TypedResponse
中的参数泛型类型, 并返回TypedResponse
- 如果返回值是其它类型(非TypedResponse和Response), 那么直接将响应体通过
Decoder.Default
解码转为返回类型, 然后直接返回 - 如果开启了解码后关闭流的动作(closeAfterDecode=treu), 那么关闭响应流(一般情况下会走这个逻辑)