当前位置: 首页 > news >正文

3.4.SynchronousMethodHandler组件之ResponseHandler

前言

feign发送完请求后, 拿到返回结果, 那么这个返回结果肯定是需要经过框架进一步处理然后再返回到调用者的, 其中ResponseHandler就是用来处理这个返回结果的, 这也是符合正常思维的处理方式, 例如springmvc部分在调用在controller端点前后都会增加扩展点。

在这里插入图片描述

从图中可以看得feign的返回处理应该不会很复杂, 并且可以自定义日志对象,和日志级别,对返回值进行解码, 并允许我们使用责任链来处理返回结果。

代码解析

老规矩, 查看类结构, 看该对象给我们提供了哪些功能

ResponseHandler

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类是同步请求结果处理器

  1. 它提供了一个参数非常多且唯一一的构造器
  2. 提供了一个方法handleResponse来处理返回对象

其中有个属性是ResponseInterceptor.Chain, 它是用来处理返回对象的责任链, 我们简单认识一下它

ResponseInterceptor.Chain

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中的一个接口。

  1. 定义了一个默认链Chain.DEFAULT
  2. 提供了一个获取下一节点的方法
  3. ResponseInterceptor提供了一个静态的包装方法andThen, 用来拦截器套娃
  4. ResponseInterceptor提供了一个用于执行拦截器的方法intercept,
  5. ResponseInterceptor提供了一个用于执行责任链的方法apply

它在Feign.Builder的父类BaseBuilder中实例化

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);}
}

该方法responseInterceptorChainprotected修饰的, 子类可以重写它。允许我们每次添加一个拦截器, 或者直接全部替换。

构建责任链

ResponseInterceptor.Chain executionChain = this.responseInterceptors.stream().reduce(ResponseInterceptor::andThen).map(interceptor -> interceptor.apply(endOfChain)).orElse(endOfChain);

这和我们传统看到的责任链有点不同, 传统的责任链一般是有前后节点以及上下文, 然后用责任链触发调用, 这里的区别在于责任链中节点的构建方式有点不同, 这里是嵌套包装的性质.

为了让大家更好的理解这坨代码, 我把它平铺开, 写段伪代码

public ResponseInterceptor.Chain buildRespChain() {ResponseInterceptor.Chain endOfChain = ResponseInterceptor.Chain.DEFAULT;// 合并所有拦截器成一个 ResponseInterceptorResponseInterceptor combinedInterceptor = null;for (ResponseInterceptor interceptor : this.responseInterceptors) {if (combinedInterceptor == null) {combinedInterceptor = interceptor;} else {ResponseInterceptor previousCombinedInterceptor = combinedInterceptor;combinedInterceptor = new ResponseInterceptor() {@Overridepublic Object intercept(InvocationContext ic, Chain chain) throws Exception {return previousCombinedInterceptor.intercept(ic, new Chain() {@Overridepublic Object next(InvocationContext context) throws Exception {return interceptor.intercept(context, chain);}});}};}}// 如果没有拦截器,直接返回 endOfChainif (combinedInterceptor == null) {return endOfChain;}ResponseInterceptor temp = combinedInterceptor;// 使用 apply 构造最终责任链return new ResponseInterceptor.Chain() {@Overridepublic Object next(InvocationContext request) throws Exception {return temp.intercept(request, endOfChain);}};}
  1. 核心就是调用每一个过滤节点(这里是拦截器)的时候把下一个传节点封装成Chain传进去, 然后我们就可以在拦截器的intercept方法中通过Chain的next方法调用下一个节点拦截器了

  2. 最后再构建一个最终的Chain, 在next方法中调用构建出来的拦截器链, 并传入默认节点endOfChain, 也就是说我们自定义的拦截器会先执行

  3. 最后再执行这个endOfChain(取决于各拦截器执行的行为, 可以决定是正序还是倒序), 返回整条连的执行结果

说到责任链, 我的这篇文章也介绍了一个我之前写的拦截器项目通用责任链在项目中使用

那么这个默认的的节点endOfChain长什么样呢? 我们得研究一下

InvocationContext

它是用lambda表达式表示的一个Chain, 定义在响应链处理器的最后一个节点, 用来处理最终的返回结果

关于lambda表达式, 如果大家不是很了解, 可以去看看我的这篇文章 lambda表达式原理

Chain DEFAULT = InvocationContext::proceed;
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) {// 抛异常, 这里可能是重试异常RetryableExceptionthrow 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>中的TType bodyType = Types.resolveLastTypeParameter(returnType, TypedResponse.class);// 这里把response解码成TypedResponse<T>中的T类型然后设置给bodyreturn 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, 直接返回responseif (!shouldDisconnectResponseBody) {return response;}try {// 响应数据不为空且小于8k, 将数据流取出来final byte[] bodyData = Util.toByteArray(response.body().asInputStream());return response.toBuilder().body(bodyData).build();} finally {// 关闭响应流(inputStream)ensureClosed(response.body());}}

小结一下

  1. 如果返回类型是Response类型
  • 如果返回数据小于8k, 证明数据已经返回完了, 不需要再读取数据;
  • 否则返回response本身继续读取数据, 并关闭响应流数据
  1. 如果请求失败, 如下情况将会抛异常(可能是重试异常)
  • 状态码不是200-300 并且 2.状态码不是404; 例如status为500
  • 状态码不是200-300 并且 状态码是404且dismiss404为false
  • 状态码不是200-300 并且 状态码是404且dismiss404为true 并且 返回类型是void类型
  • 当方法返回值不是空时, 如果不想404报错, dismiss404参数设置为true就行
  1. 如果返回类型是void, 并且不允许对void类型进行解码, 直接关闭流
  2. 如果返回值类型是TypedResponse, 那么对返回数据的body解码(这里只支持String和byte[]类型)
  3. 如果返回类型既不是Response, 也不是TypedResponse, 直接将返回的响应体数据解码成方法返回类型(这里只支持String和byte[]类型)

这里正常数据解码器是Decoder.Default, 异常数据解码器是ErrorDecoder.Default

泛型的解析工具类是Types, 如果想要更多的了解泛型, 可以看我的这篇文章 java泛型探究

这里了解一下ResponseTypedResponse的区别

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;}
}
  1. Response实现了Closeable接口, 可以用来自动关闭流
  2. Response的响应体对象是一个Body类型, 而TypedResponse的响应体是一个泛型, 该泛型是body进行解码转换的结果

关于异常状态码的响应的重试处理

// 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; 如果有重试, 会抛RetryableExceptionreturn errorDecoder.decode(methodKey, response);
} finally {// 关闭响应流(inputStream)ensureClosed(response.body());
}
}

这里errorDecoder默认是ErrorDecoder.Default

public class Default implements ErrorDecoder {public Exception decode(String methodKey, Response response) {FeignException exception = errorStatus(methodKey, response, maxBodyBytesLength,maxBodyCharsLength);// 重试的时间 毫秒类型; RETRY_AFTER:Retry-AfterLong 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;}
}
  1. 这里retryAfterDecoder默认是ErrorDecoder.RetryAfterDecoder, 定义在RetryAfterDecoder内部.

  2. firstOrNull(response.headers(), RETRY_AFTER)用与获取响应头中Retry-After属性的值(毫秒)

  3. 如果返回了正确的重试时间, 那么抛RetryableException异常, 否则抛FeignException异常

static class RetryAfterDecoder {public Long apply(String retryAfter) {if (retryAfter == null) {return null;}// 也就是数字 或者数字.?0*, 例如 匹配:100、100.、100.0、100.00if (retryAfter.matches("^[0-9]+\\.?0*$")) {// 去掉小数部分, 例如 100.00 -> 100retryAfter = 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;}}
}
  1. 响应头Retry-After属性为空或者对应的值为空, 返回null
  2. 如果``Retry-After属性返回的是数字, 那么计算下次重试的时间点(当前时间+响应头Retry-After`设置的时间)
  3. 如果``Retry-After属性返回的是时间格式, 那么它只能是Day-of-Week, DD Month YYYY HH:mm:ss GMT 例如Tue, 3 Jun 2008 11:05:30 GMT`这种

响应责任链的部分介绍完了, 下面回归到ResponseHandler

ResponseHandler详情

ResponseHandler

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;}}

小结一下

  1. 打印相应结果相关的日志
  2. 使用响应责任链处理返回结果
  3. 异常情况打印日志然后包装成FeignException异常异常抛出
  4. 最后关闭响应流

logAndRebufferResponseIfNeeded

在执行相应责任链调用之前, 先打印了一段日志

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);}
  1. 默认的logLevelLevel.NONE, 也就是不打印日志
  2. 以Slf4j举例, Slf4j在feign中是以Slf4jLogger对象存在, 它包装了org.slf4j.Logger对象

Slf4jLogger#logAndRebufferResponse

@Overrideprotected 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;}

举例

@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不报错

总结

  1. Feign通过ResponseHandler来处理响应的结果
  2. ResponseHandler主要通过响应责任链来处理响应结果, 我们可以自定义其中的节点拦截器做自定义的事情
  3. 责任链中默认添加了一个节点InvocationContext, 用来真正处理返回结果
  • 如果方法返回类型是Response, 那么根据返回的数据长度是否为空或者大于8k, 如果满足, 直接返回Response, 如果小于8k, 重新构建新的Response返回, 并关闭响应流; 这里重新构建是因为流只能读取一次, 如果关闭了就读取不到了
  1. 如果状态码是404, 并且设置dismiss404为true, 那么将忽略异常
  2. 如果状态码不是200-300也不是404, 那么根据响应头是否鞋带Retry-After, 并有正确的值, 那么将会抛重试异常进行重试, 否则抛FeignException, 然后关闭响应流
  3. 如果返回类型是void, 并且不允许对void进行编码(decodeVoid=false), 那么关闭响应流, 直接返回null
  4. 如果方法返回值是TypedResponse类型, 那么将响应体通过Decoder.Default解码转为TypedResponse中的参数泛型类型, 并返回TypedResponse
  5. 如果返回值是其它类型(非TypedResponse和Response), 那么直接将响应体通过Decoder.Default解码转为返回类型, 然后直接返回
  6. 如果开启了解码后关闭流的动作(closeAfterDecode=treu), 那么关闭响应流(一般情况下会走这个逻辑)
http://www.lryc.cn/news/493068.html

相关文章:

  • Linux 下进程的状态
  • 【计算机网络】核心部分复习
  • Spring Boot开发实战:从入门到构建高效应用
  • pyshark安装使用,ubuntu:20.04
  • 基本功能实现
  • 《那个让服务器“跳舞”的bug》
  • Python 网络爬虫进阶:动态网页爬取与反爬机制应对
  • 创建可直接用 root 用户 ssh 登陆的 Docker 镜像
  • wordpress 中添加图片放大功能
  • 数据结构 (7)线性表的链式存储
  • 库的操作.
  • Vue进阶之Vue CLI服务—@vue/cli-service Vuex
  • 导入100道注会cpa题的方法,导入试题,自己刷题
  • 数据库操作、锁特性
  • 学习笔记039——SpringBoot整合Redis
  • (笔记)简单了解ZYNQ
  • 大众点评小程序mtgsig1.2算法
  • 七牛云AIGC内容安全方案助力企业合规创新
  • .net的winfrom程序 窗体透明打开窗体时出现在屏幕右上角
  • 基于YOLOv8深度学习的智慧课堂教师上课行为检测系统研究与实现(PyQt5界面+数据集+训练代码)
  • 使用 Tkinter 创建一个简单的 GUI 应用程序来合并视频和音频文件
  • 【C++笔记】模板进阶
  • Soul App创始人张璐团队亮相GITEX GLOBAL 2024,展示多模态AI的交互创新
  • ffmpeg.wasm 在浏览器运行ffmpeg操作视频
  • 用Python爬虫“偷窥”1688商品详情:一场数据的奇妙冒险
  • CentOS上如何离线批量自动化部署zabbix 7.0版本客户端
  • 【开源项目】ChinaAddressCrawler 中国行政区划数据(1980-2023年)采集及转换(Java版),含SQL格式及JSON格式
  • React中事件处理和合成事件:理解与使用
  • Local Changes不展示,DevEco Studio的git窗口中没有Local Changes
  • 大数据笔记