3.4.SynchronousMethodHandler组件之ResponseHandler

前言

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

  1. 它提供了一个参数非常多且唯一一的构造器
  2. 提供了一个方法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中的一个接口。

  1. 定义了一个默认链Chain.DEFAULT
  2. 提供了一个获取下一节点的方法
  3. ResponseInterceptor提供了一个静态的包装方法andThen, 用来拦截器套娃
  4. ResponseInterceptor提供了一个用于执行拦截器的方法intercept,
  5. 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);
  }
}

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

构建责任链

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);
            }
        };
    }
  1. 核心就是调用每一个过滤节点(这里是拦截器)的时候把下一个传节点封装成Chain传进去, 然后我们就可以在拦截器的intercept方法中通过Chain的next方法调用下一个节点拦截器了

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

  3. 最后再执行这个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());
    }
  }

小结一下

  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的区别

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;
    }
}
  1. Response实现了Closeable接口, 可以用来自动关闭流
  2. 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;
    }
}
  1. 这里retryAfterDecoder默认是ErrorDecoder.RetryAfterDecoder, 定义在RetryAfterDecoder内部.

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

  3. 如果返回了正确的重试时间, 那么抛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;
      }
    }
}
  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

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

小结一下

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

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);
  }
  1. 默认的logLevelLevel.NONE, 也就是不打印日志
  2. 以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不报错

总结

  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), 那么关闭响应流(一般情况下会走这个逻辑)
相关推荐
BAGAE1 小时前
tomcat,appche,nginix,jboss区别
java·linux·数据库·ubuntu·tomcat
GGBondlctrl1 小时前
【Spring MVC】如何获取cookie/session以及响应@RestController的理解,Header的设置
java·spring·mvc·cookie·session·header·restcontroller
大梦百万秋1 小时前
Spring Boot开发实战:从入门到构建高效应用
java·spring boot
数懒女士1 小时前
Java常见的锁策略
java·算法
大只因bug1 小时前
基于Springboot的流浪宠物管理系统
java·spring boot·mysql·流浪宠物管理系统·在线宠物管理系统源码·javaweb宠物管理系统·宠物网站系统源码
疯一样的码农1 小时前
Maven 如何配置忽略单元测试
java·单元测试·maven
老牛源码1 小时前
Z2400024基于Java+SSM+mysql+maven开发的社区论坛系统的设计与实现(附源码 配置 文档)
java·mysql·maven·ssm
zhangj11251 小时前
Java部分新特性
java·开发语言
暮湫2 小时前
异常处理(6)自定义异常
java
不修×蝙蝠2 小时前
数据结构--链表实现栈和队列
java·数据结构·链表··队列