背景

前面几篇分析了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. Codechef - N Triplets(构造+观察)

    题目大意   对于一个正整数N,需要找到三个不同的数字A,B,C,使得三个数当中任意两个数字相乘都是N的约数,另外还要使得A,B,C三个数字乘积是N的整数倍数.最后输出三个数字(如果有多种组合,输出任 ...

  2. client-go实战之六:时隔两年,刷新版本继续实战

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 时隔两年,<client-go实战>被激活 ...

  3. 文心一言 VS 讯飞星火 VS chatgpt (117)-- 算法导论10.3 2题

    二.用go语言,对一组同构对象用单数组表示法实现,写出过程ALLOCATE-OBJECT和FREE-OBJECT. 文心一言: 在Go语言中,我们通常使用指针和结构体来表示和操作复杂的数据结构.对于一 ...

  4. 新手面对安卓6.0以上的版本时出现一个关于文件权限检测的问题,报错为:“无法解析符号 'checkSelfPermission'”,解决办法

    [[注意]:这只是笔者在遇到这个问题时的解决方法,如果对您毫无帮助,请自寻他法!!!] 面对新手:在简单做一个音乐播放程序时,如果面对安卓6.0以上的版本,就会出现一个关于文件权限检测的问题,报错为: ...

  5. C++基础杂记(2)

    将数组传入函数 禁止修改数组的值 函数的地址与函数的指针 函数的指针数组 函数的 static 与 inline 引用左值和引用传参 C++11 的数组 for 循环 64 位 Linux 操作系统中 ...

  6. C/C++ __builtin 超实用位运算函数总结

    以 __builtin 开头的函数,是一种相当神奇的位运算函数,下面本人盘点了一下这些以 __builtin 开头的函数,希望可以帮到大家. 1 __builtin_ctz( ) / __buitli ...

  7. Lyndon 分解

    介绍 [模板]Lyndon 分解 #include<cstdio> #include<cstring> char s[5000005]; int main(){ scanf(& ...

  8. 工厂模式(Factory Method)

    模式定义 定义一个用于创建对象的接口,让子类决定实例化哪一个类.Factory Method使得一个类的实例化延迟(目的:解耦)到子类. 要点总结 Factory Method模式用于隔离类对象的使用 ...

  9. 五分钟 k8s 实战-应用探针

    今天进入 kubernetes 的运维部分(并不是运维 kubernetes,而是运维应用),其实日常我们大部分使用 kubernetes 的功能就是以往运维的工作,现在云原生将运维和研发关系变得更紧 ...

  10. Object.assign () 和深拷贝

    先看看啥叫深拷贝?啥叫浅拷贝? 假设B复制了A,修改A的时候,看B是否发生变化: 如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值) 如果B没有改变,说明是深拷贝,自食其力!(修改堆 ...