专栏目录
44.避免链路信息丢失做的设计(2) 37. 实现异步的客户端封装配置管理的意义与设计 41. SpringCloudGateway 基本流程讲解(1) 42.SpringCloudGateway 现有的可供分析的请求日志以及缺陷 4.maven依赖回顾以及项目框架结构 39. 改造 resilience4j 粘合 WebClient 45. 实现公共日志记录 13.UnderTow 核心配置 5.所有项目的parent与spring-framework-common说明 30. FeignClient 实现重试 35. 验证线程隔离正确性 11.Log4j2 监控相关 23.订制Spring Cloud LoadBalancer 38. 实现自定义 WebClient 的 NamedContextFactory 14.UnderTow AccessLog 配置介绍 21.Spring Cloud LoadBalancer简介 41. SpringCloudGateway 基本流程讲解(2) 40. spock 单元测试封装的 WebClient(下) 20. 启动一个 Eureka Server 集群 17.Eureka的实例配置 15.UnderTow 订制 18.Eureka的客户端核心设计和配置 28.OpenFeign的生命周期-进行调用 22.Spring Cloud LoadBalancer核心源码 16.Eureka架构和核心概念 44.避免链路信息丢失做的设计(1) 40. spock 单元测试封装的 WebClient(上) 8.理解 NamedContextFactory 3.Eureka Server 与 API 网关要考虑的问题 26.OpenFeign的组件 27.OpenFeign的生命周期-创建代理 10.使用Log4j2以及一些核心配置 33. 实现重试、断路器以及线程隔离源码 43.为何 SpringCloudGateway 中会有链路信息丢失 19.Eureka的服务端设计与配置 29.Spring Cloud OpenFeign 的解析(1) 24.测试Spring Cloud LoadBalancer 1. 背景 25.OpenFeign简介与使用 12.UnderTow 简介与内部原理 7.从Bean到SpringCloud 2.微服务框架需要考虑的问题 6.微服务特性相关的依赖说明 32. 改进负载均衡算法 34.验证重试配置正确性 36. 验证断路器正确性 31. FeignClient 实现断路器以及线程隔离限流的思路 9.如何理解并定制一个Spring Cloud组件

28.OpenFeign的生命周期-进行调用

unknown
• 阅读 622

28.OpenFeign的生命周期-进行调用

本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent

接下来,我们开始分析 OpenFeign 同步环境下的生命周期的第二部分,使用 SynchronousMethodHandler 进行实际调用,其流程可以总结为:

  1. 调用代理类的方法实际调用的是前面一章中生成的 InvocationHandlerinvoke 方法。
  2. 默认实现是查询 Map<Method, MethodHandler> methodToHandler 找到对应的 MethodHandler 进行调用,对于同步 Feign,其实就是 SynchronousMethodHandler
  3. 对于 SynchronousMethodHandler:
    1. 使用前面一章分析创建的创建的请求模板工厂 RequestTemplate.Factory,创建请求模板 RequestTemplate
    2. 读取 Options 配置
    3. 使用配置的 Retryer 创建新的 Retryer
    4. 执行请求并将响应反序列化 - executeAndDecode:
      1. 如果配置了 RequestInterceptor,则执行每一个 RequestInterceptor
      2. 将请求模板 RequestTemplate 转化为实际请求 Request
      3. 通过 Client 执行 Request
      4. 如果响应码是 2XX,使用 Decoder 解析 Response
      5. 如果响应码是 404,并且在前面一章介绍的配置中配置了 decode404 为 true, 使用 Decoder 解析 Response
      6. 对于其他响应码,使用 errorDecoder 解析,可以自己实现 errorDecoder 抛出 RetryableException 来走入重试逻辑
      7. 如果以上步骤抛出 IOException,直接封装成 RetryableException 抛出
    5. 如果第 4 步抛出 RetryableException,则使用第三步创建的 Retryer 判断是否重试,如果需要重试,则重新走第 4 步,否则,抛出异常。

给出这个流程后,我们来详细分析

OpenFeign的生命周期-进行调用源码分析

前面一章的最后,我们已经从源码中看到了这一章开头提到的流程的前两步,我们直接从第三步开始分析。

SynchronousMethodHandler

public Object invoke(Object[] argv) throws Throwable {
    //使用前面一章分析创建的创建的请求模板工厂 `RequestTemplate.Factory`,创建请求模板 `RequestTemplate`。
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    //读取 Options 配置
    Options options = findOptions(argv);
    //使用配置的 Retryer 创建新的 Retryer
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        //执行请求并将响应反序列化
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        //如果抛出 RetryableException,则使用 retryer 判断是否重试,如果需要重试,则继续请求即重试,否则,抛出异常。
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

对于 executeAndDecode 其中的源码,为了兼容异步 OpenFeign 兼容 CompletableFuture 的特性,做了一些兼容性修改导致代码比较难以理解,由于我们这里不关心异步 Feign,所以我们将这块代码还原回来,在这里展示:

这个修改对应的 Issue 和 PullRequest 是:

Request targetRequest(RequestTemplate template) {
    //如果配置了 RequestInterceptor,则执行每一个 RequestInterceptor
    for (RequestInterceptor interceptor : requestInterceptors) {
      interceptor.apply(template);
    }
    //将请求模板 RequestTemplate 转化为实际请求 Request
    return target.apply(template);
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
      //通过 Client 执行 Request
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 12
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

    boolean shouldClose = true;
    try {
      if (logLevel != Logger.Level.NONE) {
        response =
            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
      }
      //如果响应码是 2XX,使用 Decoder 解析 Response
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          Object result = decode(response);
          shouldClose = closeAfterDecode;
          return result;
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        //如果响应码是 404,并且在前面一章介绍的配置中配置了 decode404 为 true, 使用 Decoder 解析 Response
        Object result = decode(response);
        shouldClose = closeAfterDecode;
        return result;
      } else {
        //对于其他响应码,使用 errorDecoder 解析,可以自己实现 errorDecoder 抛出 RetryableException 来走入重试逻辑
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      //如果抛出 IOException,直接封装成 RetryableException 抛出
      throw errorReading(request, response, e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
}

static FeignException errorReading(Request request, Response response, IOException cause) {
    return new FeignException(
        response.status(),
        format("%s reading %s %s", cause.getMessage(), request.httpMethod(), request.url()),
        request,
        cause,
        request.body(),
        request.headers());
}

这样,我们就分析完 OpenFeign 的生命周期

28.OpenFeign的生命周期-进行调用

我们这一节详细介绍了 OpenFeign 进行调用的详细流程。接下来我们将开始介绍,spring-cloud-openfeign 里面,是如何定制 OpenFeign 的组件并粘合的。

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer

28.OpenFeign的生命周期-进行调用

点赞
收藏
评论区
推荐文章

暂无数据

unknown
unknown
Lv1
男 · rrrr · rrrrrrrr
rrrrr
文章
0
粉丝
17
获赞
0
热门文章

暂无数据