背景

前面几篇分析了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:调用过程的更多相关文章

  1. Feign源码解析

    1. Feign源码解析 1.1. 启动过程 1.1.1. 流程图 1.1.2. 解释说明 Feign解析过程依赖Spring的初始化,它通过实现ImportBeanDefinitionRegistr ...

  2. Dubbo 源码分析 - 服务调用过程

    注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...

  3. Feign源码解析系列-注册套路

    感谢不知名朋友的打赏,感谢你的支持! 开始 在追寻Feign源码的过程中发现了一些套路,既然是套路,就可以举一反三,所以值得关注. 这篇会详细解析Feign Client配置和初始化的方式,这些方式大 ...

  4. Feign源码解析系列-那些注解们

    开始 Feign在Spring Cloud体系中被整合进来作为web service客户端,使用HTTP请求远程服务时能就像调用本地方法,可见在未来一段时间内,大多数Spring Cloud架构的微服 ...

  5. Feign源码解析系列-最佳实践

    前几篇准备写完feign的源码,这篇直接给出Feign的最佳实践,考虑到目前网上还没有一个比较好的实践解释,对于新使用spring cloud的同学会对微服务之间的依赖产生一些迷惑,也会走一些弯路.这 ...

  6. mybatis源码解析7---MappedStatement初始化过程

    上一篇我们了解到了MappedStatement类就是mapper.xml中的一个sql语句,而Configuration初始化的时候会加载所有的mapper接口类,而本篇再分析下是如何将mapper ...

  7. Dubbo源码(九) - 服务调用过程

    1. 前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 源码分析均基于官方Demo,路径:dubbo/dubbo-demo 如果没有看过之前Dub ...

  8. alluxio源码解析-rpc调用概述-client和worker之间的block模块的通讯架构(netty版本)(3)

    (1.8版本)client和worker之间的block模块的通讯架构 block作为alluxio文件读取或者存储的最小基本单位,都是通过BlockOutStream和BlockInputtream ...

  9. Feign源码解析系列-核心初始化

    开始 初始化Feign客户端当然是整个过程中的核心部分,毕竟初始化完毕就等着调用了,初始化时候准备的什么,流程就走什么. 内容 从上一篇中,我们已经知道,对于扫描到的每一个有@FeignClient, ...

  10. SuperSocket源码解析之启动过程

    一 简介 这里主要说明从配置系统引导启动SuperScoekt作为应用程序,且以控制台程序方式启动 二 启动过程 2.1 配置解析 从读取配置文件开始,直接拿到一个SocketServiceConfi ...

随机推荐

  1. Python并发编程——threading、开启线程 、线程进程对比、线程方法、守护线程、GIL、同步锁、死锁和递归锁、信号量、Event、condition、定时器、queue、concurrent

    文章目录 内容回顾 一 threading模块介绍 二 开启线程的两种方式 三 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别 四 练习 五 线程相关的其他方法 六 守护线程 七 Pyth ...

  2. PostgreSQL学习笔记-1.基础知识:创建、删除数据库和表格

    PostgreSQL 创建数据库 PostgreSQL 创建数据库可以用以下三种方式:1.使用 CREATE DATABASE SQL 语句来创建.2.使用 createdb 命令来创建.3.使用 p ...

  3. 【算法】游戏中的学习,使用c#面向对象特性控制游戏角色移动

    最近,小悦的生活像是一首繁忙的交响曲,每天忙得团团转,虽然她的日程安排得满满当当,但她并未感到充实.相反,她很少有时间陪伴家人,这让她感到有些遗憾.在周五的午后,小悦的哥哥突然打来电话,他的声音里充满 ...

  4. nginx中一个请求匹配到多个location时的优先级问题,马失前蹄了

    背景 为什么讲这么小的一个问题呢?因为今天在进行系统上线的时候遇到了这个问题. 这次的上线动作还是比较大的,由于组织架构拆分,某个接入层服务需要在两个部门各自独立部署,以避免频繁的跨部门沟通,提升该接 ...

  5. trafilatura 网页解析原理分析

    trafilatura 介绍 Trafilatura是一个Python包和命令行工具,用于收集网络上的文本.其主要应用场景包括网络爬虫下载和网页解析等. 今天我们不讨论爬虫和抓取,主要看他的数据解析是 ...

  6. 【源码解读(二)】EFCORE源码解读之查询都做了什么以及如何自定义批量插入

    引言 书接上回,[源码解读(一)]EFCORE源码解读之创建DBContext查询拦截,在上一篇文章中,主要讲了DBContext的构造函数,以及如何缓存查询方法提升查询性能,还有最重要的拦截查询,托 ...

  7. 终端必备大杀器----Fish

    目录 下载 安装 添加 权限 依赖库安装 cmake 预处理 编译 安装 配置fish 其他 下载 Github 地址-- fish-shell openSUSE 开源下载地址 openSUSE 开源 ...

  8. Java核心知识体系7:线程安全性讨论

    Java核心知识体系1:泛型机制详解 Java核心知识体系2:注解机制详解 Java核心知识体系3:异常机制详解 Java核心知识体系4:AOP原理和切面应用 Java核心知识体系5:反射机制详解 J ...

  9. WPF应用开发之附件管理

    在我们之前的开发框架中,往往都是为了方便,对附件的管理都会进行一些简单的封装,目的是为了方便快速的使用,并达到统一界面的效果,本篇随笔介绍我们基于SqlSugar开发框架的WPF应用端,对于附件展示和 ...

  10. 吉客云与用友U8的系统数据集成对接方案

    吉客云与用友U8之间的系统数据集成方案.吉客云作为一款电商ERP产品,旨在为企业的数字化升级提供全方位的支持.用友U8是一个经过多年发展的信息化管理系统,见证了企业信息化从简单到精细.从局部到全面的转 ...