本系列代码地址: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:

    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 抛出
  8. 如果第 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 的生命周期

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

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

SpringCloud升级之路2020.0.x版-28.OpenFeign的生命周期-进行调用的更多相关文章

  1. SpringCloud升级之路2020.0.x版-27.OpenFeign的生命周期-创建代理

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 接下来,我们开始分析 OpenFeign 的生命周期,结合 OpenFeign 本身的源代 ...

  2. SpringCloud升级之路2020.0.x版-26.OpenFeign的组件

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 首先,我们给出官方文档中的组件结构图: 官方文档中的组件,是以实现功能为维度的,我们这里是 ...

  3. SpringCloud升级之路2020.0.x版-25.OpenFeign简介与使用

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent OpenFeign 的由来和实现思路 在微服务系统中,我们经常会进行 RPC 调用.在 S ...

  4. SpringCloud升级之路2020.0.x版-1.背景

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ Spring ...

  5. SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 接下来,将进入我们升级之路的又一大模块,即网关模块.网关模块我们废弃了已经进入维护状态的 ...

  6. SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...

  7. SpringCloud升级之路2020.0.x版-10.使用Log4j2以及一些核心配置

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Log4 ...

  8. SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...

  9. SpringCloud升级之路2020.0.x版-7.从Bean到SpringCloud

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ 在理解 Spr ...

随机推荐

  1. Qt5之坐标系统

    窗口坐标为逻辑坐标,是基于视口坐标系的. 视口坐标为物理坐标,是基于绘图设备坐标系的 窗口坐标始终以视口坐标为最终目标进行映射: QPainter::setWindow 修改了窗口位置和大小(左上角重 ...

  2. 一文读懂Lua元表

    元表 Lua语言中的每种类型的值都有一套可预见的操作集合.例如,我们可以将数字相加,可以连接字符串,还可以在表中插入键值对等,但是我们无法将两个表相加,无法对函数作比较,也无法调用一个字符串,除非使用 ...

  3. WHY IS A BYTE 8 BITS? OR IS IT?

    WHY IS A BYTE 8 BITS? OR IS IT? 原文链接:http://www.bobbemer.com/BYTE.HTM I recently received an e-mail ...

  4. TDSQL(MySQL版)之DB组件升级

    随着数据库产品的更新迭代,修复bug等等,产品避免不了会出现升级的需求.TDSQL(MysqL版)也会有这方面的需求.接下来我就说说如何对现有TDSQL(MySQL版)集群组件进行升级,而不影响业务. ...

  5. Python 高级特性(5)- 迭代器 Iterator

    前言 迭代器貌似是 Python3 才有的(猜的),在廖雪峰大神的网站中 Python2 是没有迭代器一栏的 可 for 循环的对象 常见集合数据类型(迭代对象):list.tuple.dict.se ...

  6. ElasticAlert基于聚合告警

    背景 最近公司网站经常被漏洞扫描,虽然并没有什么漏洞给对方利用,但是每次扫描我们也必须要察觉到,如果扫描的量太大,可以考虑从公有云的安全组上禁用掉这个IP,所以需要统计指定时间内每个IP的访问次数,这 ...

  7. Linux上项目部署在home目录中无法访问的问题

    在Linux上开发一个Web项目,使用nginx作为Web服务器.在nginx的配置文件中添加一个server,root路径写的是放在home目录中的项目目录的路径.打开浏览器访问,提示错误:403 ...

  8. Elasticsearch(ES)分词器的那些事儿

    1. 概述 分词器是Elasticsearch中很重要的一个组件,用来将一段文本分析成一个一个的词,Elasticsearch再根据这些词去做倒排索引. 今天我们就来聊聊分词器的相关知识. 2. 内置 ...

  9. PTA——c++函数

    1.在C++中,关于下列设置缺省参数值的描述中,()是正确的. 在指定了缺省值的参数右边,不能出现没有指定缺省值的参数: 2.使用地址作为实参传给形参,下列说法正确的是() 实参与形参操作的是同一对象 ...

  10. 回忆之placeholder

    直接看效果点这里 HTML <!DOCTYPE html> <html> <head lang="zh-CN"> <meta charse ...