Feign源码解析4:调用过程
背景
前面几篇分析了Feign的初始化过程,历经艰难,可算是把@FeignClient注解的接口对应的代理对象给创建出来了。今天看下在实际Feign调用过程中的一些源码细节。
我们这里Feign接口如下:
@FeignClient(value = "echo-service-provider") // 指向服务提供者应用
public interface EchoService {
@GetMapping("/echo/{message}")
String echo(@PathVariable("message") String message);
}
调用代码:
http://localhost:8080/feign/echo/ddd
@GetMapping("/feign/echo/{message}")
public String feignEcho(@PathVariable String message) {
return echoService.echo(message);
}
完整解析
动态代理
这里的echoService此时的类型其实是jdk动态代理,内部有一个字段,就是实现了jdk的InvocationHandler接口:

实际逻辑如下,会根据被调用的method找到一个合适的handler:
import feign.InvocationHandlerFactory.MethodHandler
static class FeignInvocationHandler implements InvocationHandler{
private final Map<Method, MethodHandler> dispatch;
}

一般获取到的都是如下类型:

这个类有如下核心属性:

都是和http请求息息相关的,如重试、请求拦截器、响应拦截器、logger、logger级别、options(包含了超时时间等参数)。
重试
接下来看实际请求的大体框架:

上面主要是一个while循环,内部会执行请求,如果请求报错,抛出了RetryableException类型的异常,此时就会由重试组件(Retryer retryer),判断是否要重试,如果要的话,就会continue,就会再次执行请求。
但如果重试组件认为不需要重试或重试次数已经超过,就会抛出异常,此时就走不到continue部分了,会直接向上层抛异常。

注意,这个重试接口实现了Cloneable,因为每次请求的时候,都要有一个对应的重试对象来记录当前请求的重试状态(比如重试了几次了),正因为有状态,所以得每次clone一个新的。
Retryer retryer = this.retryer.clone();
默认情况下,不做任何配置,都是不重试的,此时的retryer类型为一个内部类,意思是NEVER_RETRY:

这里再拓展一点,什么情况下,Feign会抛出RetryableException呢?
其实就是在执行上图的正常执行部分,遇到了java.io.IOException的时候,就会抛这种RetryableException。

static FeignException errorExecuting(Request request, IOException cause) {
return new RetryableException(
-1,
format("%s executing %s %s", cause.getMessage(), request.httpMethod(), request.url()),
request.httpMethod(),
cause,
null, request);
}
还有一种情况是啥呢?是服务端返回的http header中包含了Retry-After这个header的时候:

这个header一般是在503的时候返回:

根据模版创建请求
大家看看我们的接口,用了path variable,所以,在真正进行请求前,必须先完成一些准备工作,比如把path variable替换为真实的值:
@GetMapping("/echo/{message}")
String echo(@PathVariable("message") String message);

另外,大家看代码,还有个参数传给下层:


这个就是控制请求超时参数的。这个options从哪里来呢,从我们的传参来。我们可以在方法中加一个参数:
@GetMapping("/echo/{message}")
String echo(@PathVariable("message") String message, Request.Options options);
这个的优先级,我觉得应该是最高的。
这样就能支持,每个接口用不一样的超时时间。
executeAndDecode概览
生成绝对路径请求

先说说1处:
Request targetRequest(RequestTemplate template) {
// 使用请求拦截器
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(template);
}
请求拦截器的类型:
public interface RequestInterceptor {
/**
* Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
*/
void apply(RequestTemplate template);
}
这里是对模版进行修改,我看注释,有如下场景(增加全局的header):

接下来,再看看模版如何转化为请求:
return target.apply(template);

结果,其实也没干啥,就是模版里只有接口的相对路径,此处要拼接为完整路径:

client.execute接口
实际执行请求,靠client对象,它的默认类型为:

它是自动装配的:

它内部包裹了实际发http请求的库,上面的代码中,默认用的是feign自带的(位于feign-core依赖):
new Client.Default(null, null)

比如,假设我们想用httpclient (在classpath中包含),就会触发自动装配:


也支持okhttp(feign.okhttp.enabled为true):

这里看看FeignBlockingLoadBalancerClient的几个构造参数:
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancerClientFactory = loadBalancerClientFactory;
}
第一个是上面提到的http客户端,第二个、第三个呢,其实是负责负载均衡的客户端(不只是要负责客户端负载均衡算法,还要负责从nacos这些地方获取服务对应的实例)

client获取服务实例
这个execute方法实际有两部分,一部分是如下,根据service名称,获取服务实例(包含了真实的ip、端口),再一部分才是对服务实例发起请求。
下面的1处,是获取一些listener,然后通知listener,我们已经开始请求了,这里的listener的类型是:
org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle
包含了好些个生命周期方法,如onStart方法:
void onStart(Request<RC> request);

然后是2处,利用loadBalancerClient,根据服务名,如我们这里的echo-service-provider,获取到一个实例(类型为org.springframework.cloud.client.ServiceInstance)。
3处会判断,如果没获取到实例,此时就会报503了,服务实例不存在。
这里面,获取实例的部分,比较复杂,我们得单独开一篇来讲。
发起真实请求
根据服务实例,组装真实的url进行请求。

这个等负载均衡部分写完了,再讲解这部分,这块的逻辑也还好,无非是对httpclient这些的封装。
总结
我们把整体的feign调用的脉络梳理了一遍,下篇继续loadbalancer的部分,那里也是有点坑的。
用讲师的话来结个尾:啥也不是,散会!
Feign源码解析4:调用过程的更多相关文章
- Feign源码解析
1. Feign源码解析 1.1. 启动过程 1.1.1. 流程图 1.1.2. 解释说明 Feign解析过程依赖Spring的初始化,它通过实现ImportBeanDefinitionRegistr ...
- Dubbo 源码分析 - 服务调用过程
注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...
- Feign源码解析系列-注册套路
感谢不知名朋友的打赏,感谢你的支持! 开始 在追寻Feign源码的过程中发现了一些套路,既然是套路,就可以举一反三,所以值得关注. 这篇会详细解析Feign Client配置和初始化的方式,这些方式大 ...
- Feign源码解析系列-那些注解们
开始 Feign在Spring Cloud体系中被整合进来作为web service客户端,使用HTTP请求远程服务时能就像调用本地方法,可见在未来一段时间内,大多数Spring Cloud架构的微服 ...
- Feign源码解析系列-最佳实践
前几篇准备写完feign的源码,这篇直接给出Feign的最佳实践,考虑到目前网上还没有一个比较好的实践解释,对于新使用spring cloud的同学会对微服务之间的依赖产生一些迷惑,也会走一些弯路.这 ...
- mybatis源码解析7---MappedStatement初始化过程
上一篇我们了解到了MappedStatement类就是mapper.xml中的一个sql语句,而Configuration初始化的时候会加载所有的mapper接口类,而本篇再分析下是如何将mapper ...
- Dubbo源码(九) - 服务调用过程
1. 前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 源码分析均基于官方Demo,路径:dubbo/dubbo-demo 如果没有看过之前Dub ...
- alluxio源码解析-rpc调用概述-client和worker之间的block模块的通讯架构(netty版本)(3)
(1.8版本)client和worker之间的block模块的通讯架构 block作为alluxio文件读取或者存储的最小基本单位,都是通过BlockOutStream和BlockInputtream ...
- Feign源码解析系列-核心初始化
开始 初始化Feign客户端当然是整个过程中的核心部分,毕竟初始化完毕就等着调用了,初始化时候准备的什么,流程就走什么. 内容 从上一篇中,我们已经知道,对于扫描到的每一个有@FeignClient, ...
- SuperSocket源码解析之启动过程
一 简介 这里主要说明从配置系统引导启动SuperScoekt作为应用程序,且以控制台程序方式启动 二 启动过程 2.1 配置解析 从读取配置文件开始,直接拿到一个SocketServiceConfi ...
随机推荐
- Django框架——forms组件、cookie与session
文章目录 1 forms 组件 1 校验字段功能 2 渲染标签功能 渲染方式1 渲染方式2 渲染方式3 3 渲染错误信息功能 视图 模板 4 组件的参数配置 5 局部钩子 6 全局钩子 2 cooki ...
- openssl加解密实战
一.概念 1.加密有两种类型 密钥加密(secret-key encryption)使用相同的密钥进行加密和解密,也叫对称加密 公钥加密(public-key encryption)使用不同的密钥进行 ...
- 从原理到实战,详解XXE攻击
本文分享自华为云社区<[安全攻防]深入浅出实战系列专题-XXE攻击>,作者: MDKing. 1 基本概念 XML基础:XML 指可扩展标记语言(Extensible Markup Lan ...
- Android Studio3.2.1升级刨坑记录
Android Studio出了3.2.1,我用的是2.3,所有决定升级一下,看看如何 为了保险一点,下载了官方的解压版本,也就是说不含sdk,下载android-studio-ide-181.501 ...
- GPL协议原文及中文翻译
GPL协议原文及中文翻译 原文参考链接 翻译参考链接 原文 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 19 ...
- 不写代码、构建一个开源的 ChatGPT,总共需要几步?|Hugging News #1020
每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...
- 前端工程化&&自动化部署&&model抽离
你不知道的前端 MVVM 模式中的数据层(万字长文,教你造轮子) 实现了 Model 层抽离的全部想法, 后端返回的接口--model(错误处理,返回统一格式,洗数据,缓存)--再拿这个处理过的数据 ...
- [Python急救站课程]日期和时间的输出
日期和时间的输出 from datetime import datetime # 引用datetime 库 now = datetime.now() # 获得当前日期和时间信息 print(now) ...
- 编写高性能C#代码 —— Span<T>
Span 提供任意内存的连续区域的类型安全和内存安全表示形式.它是在堆栈而不是托管堆上分配的ref结构,是对任意内存块的抽象 . 1.关于Span 在NET Core 2.1中首次引入 提供对任意内存 ...
- 标子查询优化和改写SQL案例
京华开发一哥们找我优化条报表SQL,反馈执行时间很慢需要 18s 才能出结果,安排. -- 原SQL SELECT 2 AS TYPE, to_char(a."create_time&quo ...