SpringCloud升级之路2020.0.x版-28.OpenFeign的生命周期-进行调用

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: SpringCloud升级之路2020.0.x版-28.OpenFeign的生命周期-进行调用

image.png


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

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

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


image.png


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

相关文章
|
1月前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
148 5
|
3月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
68 0
|
5月前
|
Java Spring
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
|
5月前
|
API 开发者 Java
API 版本控制不再难!Spring 框架带你玩转多样化的版本管理策略,轻松应对升级挑战!
【8月更文挑战第31天】在开发RESTful服务时,为解决向后兼容性问题,常需进行API版本控制。本文以Spring框架为例,探讨四种版本控制策略:URL版本控制、请求头版本控制、查询参数版本控制及媒体类型版本控制,并提供示例代码。此外,还介绍了通过自定义注解与过滤器实现更灵活的版本控制方案,帮助开发者根据项目需求选择最适合的方法,确保API演化的管理和客户端使用的稳定与兼容。
228 0
|
5月前
|
Java Spring 容器
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
|
5月前
|
存储 Java Spring
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
|
5月前
|
SQL Java 数据库连接
【Azure Spring Cloud】Azure Spring Cloud connect to SQL using MSI
【Azure Spring Cloud】Azure Spring Cloud connect to SQL using MSI
|
5月前
|
Java 开发工具 Spring
【Azure Spring Cloud】使用azure-spring-boot-starter-storage来上传文件报错: java.net.UnknownHostException: xxxxxxxx.blob.core.windows.net: Name or service not known
【Azure Spring Cloud】使用azure-spring-boot-starter-storage来上传文件报错: java.net.UnknownHostException: xxxxxxxx.blob.core.windows.net: Name or service not known
|
5月前
|
NoSQL Java Redis
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常
|
5月前
|
Java Spring
【Azure 应用服务】记一次Azure Spring Cloud 的部署错误 (az spring-cloud app deploy -g dev -s testdemo -n demo -p ./hellospring-0.0.1-SNAPSHOT.jar --->>> Failed to wait for deployment instances to be ready)
【Azure 应用服务】记一次Azure Spring Cloud 的部署错误 (az spring-cloud app deploy -g dev -s testdemo -n demo -p ./hellospring-0.0.1-SNAPSHOT.jar --->>> Failed to wait for deployment instances to be ready)