背景

前面几篇分析了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. 分布式与微服务——Iaas,Paas和Saas、单体应用和缺点、微服务概念、传统 分布式 SOA 架构与微服务架构的区别、微服务实战、什么是RPC、CAP定理和BASE理论、唯一ID生成、实现分布式

    文章目录 1-什么是Iaas,Paas和Saas 一 IaaS基础设施服务 二 paas平台即服务 三saas软件即服务 四 总结 2-单体应用和缺点 一 单体应用 二 单体应用的缺陷 3-微服务概念 ...

  2. 数据重整:用Java实现精准Excel数据排序的实用策略

    摘要:本文由葡萄城技术团队原创并首发.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 前言 在数据处理或者数据分析的场景中,需要对已有的数据进行排序,在Ex ...

  3. 14.1 Socket 套接字编程入门

    Winsock是Windows操作系统上的套接字API,用于在网络上进行数据通信.套接字通信是一种允许应用程序在计算机网络上进行实时数据交换的技术.通过使用Windows提供的API,应用程序可以创建 ...

  4. vue项目打包,解决静态资源无法加载和路由加载无效(404)问题

    打包后的项目静态资源无法使用,导致页面空白 静态资源无法使用,那就说明项目打包后,图片和其他静态资源文件相对路径不对,此时找到config里面的index.js,在build模块下加入assetsPu ...

  5. C#学习笔记--面向对象三大特征

    C#核心 面向对象--封装 用程序来抽象现实世界,(万物皆对象)来编程实现功能. 三大特性:封装.继承.多态. 类与对象 声明位置:namespace中 样式:class 类名{} 命名:帕斯卡命名法 ...

  6. Error in v-on handler: “TypeError: _user.default is not a function“

    碰到这个问题一开始以为是方法名重复了,后来检查了一遍也没发现方法名或者属性名重复然后发现是 这个导入方法时没加{}的问题. , 无语.

  7. 安信可开发环境构建-基于Ai-WB2系列 和 Ai-M61 或 Ai-M62 (环境上下文切换)

    首先,对于Ai-WB2系列环境的构建官方文档已经讲的非常明白了,这里不做阐述如下链接所示https://blog.csdn.net/Boantong_/article/details/12848091 ...

  8. AT通讯总结(56K猫调制解调器Modem)型号I-56EM

    1.关闭流控RTS与DTR AT&D0&K0\r\n 2.保存到非易失性存储 AT&W\r\n 3.向800001音频拨号 ATDT800001\r\n 4.接听 ATA\r\ ...

  9. Spring ---三种注入方式

    循环依赖这个问题,按理说我们在日常的程序设计中应该避免,其实这个本来也是能够避免的.不过由于总总原因,我们可能还是会遇到一些循环依赖的问题,特别是在面试的过程中,面试考察循环依赖,主要是想考察候选人对 ...

  10. RTMP协议学习——从握手到播放

    从客户端发起播放请求,到rtrmp视频流开始播放,大致经过了握手->建立连接->创建流->播放这几步比较重要的步骤.下面我将结合wireshark的抓包,对其中的每个流程进行分析和学 ...