Feign工作流程源码解析

什么是feign:一款基于注解和动态代理的声明式restful http客户端。

原理

Feign发送请求实现原理

  • 微服务启动类上标记@EnableFeignClients注解,然后Feign接口上标记@FeignClient注解。@FeignClient注解有几个参数需要配置,这里不再赘述,都很简单。

  • Feign框架会扫描注解,然后通过Feign类来处理注解,并最终生成一个Feign对象。

解析@FeignClient注解,生成MethodHandler

具体的解析类是ParseHandlerByName。这个类是ReflectiveFeign的内部类。

// 解析注解元数据,使用Contract解析
List<MethodMetadata> metadata = this.contract.parseAndValidateMetadata(key.type());

拿到注解元数据以后,循环处理注解元数据,创建每个方法对应的MethodHandler,这个MethodHandler最终会被代理对象调用。最终MethodHandler都会保存到下面这个集合中,然后返回。

Map<String, MethodHandler> result = new LinkedHashMap();
解析完成以后,调用ReflectiveFeign.newInstance()生成代理类。

MethodHandler是feign的一个接口,这个接口的invoke方法,是动态代理调用者InvocationHandler的invoke()方法最终调用的方法。

重新表述一遍:InvocationHandler的invoke()方法最终回调MethodHandler的invoke()来发送http请求。这就是Feign动态代理的具体实现。

ReflectiveFeign类的newInstance()方法的第57行:
// 创建动态代理调用者
InvocationHandler handler = this.factory.create(target, methodToHandler);
// 反射生成feign接口代理
T proxy = Proxy.newProxyInstance(加载器, 接口数组, handler);

InvocationHandler.invoke()的具体实现在FeignInvocationHandler.invoke(),FeignInvocationHandler也是ReflectiveFeign的一个内部类。里面有很多细节处理这里不再赘述,我们直接进入核心那一行代码,以免影响思路,我们是理Feign的实现原理的!不要在意这些细节!

// InvocationHandler的invoke()方法最终回调MethodHandler的invoke()来发送http请求

ReflectiveFeign类的invoke()方法,第323行,代码的后半段,如下:
(MethodHandler)this.dispatch.get(method). invoke(args);
  • this.dispatch:这是一个map,就是保存所有的MethodHandler的集合。参考创建InvocationHandler的位置:ReflectiveFeign类的newInstance()方法的第57行。

  • this.dispatch.get(method):这里的method就是我们开发者写的feign接口中定义的方法的方法名!这段代码的意思就是从MethodHandler集合中拿到我们需要调用的那个方法。

  • this.dispatch.get(method). invoke(args):这里的invoke就是调用的MethodHandler.invoke()!动态代理回调代理类,就这样完成了,oh my god,多么伟大的创举!

MethodHandler.invoke()的具体实现:SynchronousMethodHandler.invoke()

到了这里,就是发送请求的逻辑了。发送请求前,首先要创建请求模板,然后调用请求拦截器RequestInterceptor进行请求处理。

// 创建RequestTemplate
RequestTemplate template = this.buildTemlpateFromArgs.create(argv);
// 创建feign重试器,进行失败重试
Retryer retryer = this.retryer.clone();
while(true){
    try{
        // 发送请求
        return this.executeAndDecode(template);
    } catch(RetryableException var5) {
        // 失败重试,最多重试5次
        retryer.continueOrPropagate();
    }
}
RequestTemplate处理

RequestTemplate模板需要经过一系列拦截器的处理,主要有以下拦截器:

  • BasicAuthRequestInterceptor:授权拦截器,主要是设置请求头的Authorization信息,这里是base64转码后的用户名和密码。

  • FeignAcceptGzipEncodingInterceptor:编码类型拦截器,主要是设置请求头的Accept-Encoding信息,默认值{gzip, deflate}。

  • FeignContextGzipEncodingInterceptor:压缩格式拦截器,该拦截器会判断请求头中Context-Length属性的值,是否大于请求内容的最大长度,如果超过最大长度2048,则设置请求头的Context-Encoding信息,默认值{gzip, deflate}。注意,这里的2048是可以设置的,可以在配置文件中进行配置:

feign.compression.request.enabled=true
feign.compression.request.min-request-size=2048

min-request-size是通过FeignClientEncodingProperties来解析的,默认值是2048。

我们还可以自定义请求拦截器,我们自定义的拦截器,也会在此时进行调用,所有实现了RequestTemplate接口的类,都会在这里被调用。比如我们可以自定义拦截器把全局事务id放在请求头里。

使用feign.Request把RequestTemplate包装成feign.Request

feign.Request由5部分组成:

  • method

  • url

  • headers

  • body

  • charset

http请求客户端

Feign发送http请求支持下面几种http客户端:

  • JDK自带的HttpUrlConnection

  • Apache HttpClient

  • OkHttpClient

// 具体实现有2个类Client.Default 和LoadBalancerFeignClient

response = this.client.execute(request, this.options);

Client接口定义了execute()的接口,并且通过接口内部类实现了Client.execute()。

HttpURLConnection connection = this.convertAndSend(request, options);

return this.convertResponse(connection).toBuilder(). request(request).build();

  • 这里的Options定义了2个参数:

    • connectTimeoutMillis:连接超时时间,默认10秒。

    • readTimeoutMillis:读取数据超时时间,默认60秒。

这种方式是最简单的实现,但是不支持负载均衡,Spring Cloud整合了Feign和Ribbon,所以自然会把Feign和Ribbon结合起来使用。也就是说,Feign发送请求前,会先把请求再经过一层包装,包装成RibbonRequest。

也就是发送请求的另一种实现LoadBalancerFeignClient。

// 把Request包装成RibbonRequest
RibbonRequest ribbonRequest = new (this.delegate, request, uriWithoutHost);
// 配置超时时间
IClientConfig requestConfig = this.getClientConfig(options, clientName);
// 以负载均衡的方式发送请求
return ((RibbonResponse)this.IbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
以负载均衡的方式发送请求
  • this.IbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig))的具体实现在AbstractLoadBalancerAwareClient类中。

  • executeWithLoaderBalancer()方法的实现也参考了响应式编程,通过LoadBalancerCommand提交请求,然后使用Observable接收响应信息。

AbstractLoadBalancerAwareClient类的executeWithLoadBalancer()方法的第54行:

Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));

AbstractLoadBalancerAwareClient实现了IClient接口,该接口定义了execute()方法,

  • AbstractLoadBalancerAwareClient.this.execute()的具体实现有很多种:

    • OkHttpLoadBalancingClient

    • RetryableOkHttpLoadBalancingClient

    • RibbonLoadBalancingHttpClient

    • RetryableRibbonLoadBalancingHttpClient

我们以RibbonLoadBalancingHttpClient为例来说明,RibbonLoadBalancingHttpClient.execute()

第62行代码:

// 组装HttpUriRequest

HttpUriRequest httpUriRequest = request.toRequest(requestConfig);

// 发送http请求

HttpResponse httpResponse = ((HttpClient)this.delegate).execute(httpUriRequest);

// 使用RibbonApacheHttpResponse包装http响应信息

return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());

RibbonApacheHttpResponse由2部分组成:

httpResponse

uri

处理http相应

http请求经过上面一系列的转发以后,最终还会回到SynchronousMethodHandler,然后SynchronousMethodHandler会进行一系列的处理,然后响应到浏览器。

  • 注册Feign客户端bean到IOC容器

  • 查看Feign框架源代码,我们可以发现,FeignClientsRegistar的registerFeignClients()方法完成了feign相关bean的注册。

Feign架构图

  • 第一步:基于JDK动态代理生成代理类。

  • 第二步:根据接口类的注解声明规则,解析出底层MethodHandler

  • 第三步:基于RequestBean动态生成request。

  • 第四步:Encoder将bean包装成请求。

  • 第五步:拦截器负责对请求和返回进行装饰处理。

  • 第六步:日志记录。

  • 第七步:基于重试器发送http请求,支持不同的http框架,默认使用的是HttpUrlConnection。

SpringCloud-技术专区-从源码层面让你认识Feign工作流程和运作机制的更多相关文章

  1. 【SpringCloud技术专题】「Eureka源码分析」从源码层面让你认识Eureka工作流程和运作机制(上)

    前言介绍 了解到了SpringCloud,大家都应该知道注册中心,而对于我们从过去到现在,SpringCloud中用的最多的注册中心就是Eureka了,所以深入Eureka的原理和源码,接下来我们要进 ...

  2. Netty 源码解析(七): NioEventLoop 工作流程

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第七篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  3. 从RocketMQ的Broker源码层面验证一下这两个点

    本篇博客会从源码层面,验证在RocketMQ基础概念剖析,并分析一下Producer的底层源码中提到的结论,分别是: Broker在启动时,会将自己注册到所有的NameServer上 Broker在启 ...

  4. nginx源码层面探究request_time、upstream_response_time、upstream_connect_time与upstream_header_time指标具体含义

    背景概述 最近计划着重分析一下线上各api的HTTP响应耗时情况,检查是否有接口平均耗时.99分位耗时等相关指标过大的情况,了解到nginx统计请求耗时有四个指标:request_time.upstr ...

  5. Tomcat 调优之从 Linux 内核源码层面看 Tcp backlog

    前两天看到一群里在讨论 Tomcat 参数调优,看到不止一个人说通过 accept-count 来配置线程池大小,我笑了笑,看来其实很多人并不太了解我们用的最多的 WebServer Tomcat,这 ...

  6. Sentinel-Go 源码系列(二)|初始化流程和责任链设计模式

    上节中我们知道了 Sentinel-Go 大概能做什么事情,最简单的例子如何跑起来 其实我早就写好了本系列的第二篇,但迟迟没有发布,感觉光初始化流程显得有些单一,于是又补充了责任链模式,二合一,内容显 ...

  7. Android7.0 Phone应用源码分析(二) phone来电流程分析

    接上篇博文:Android7.0 Phone应用源码分析(一) phone拨号流程分析 今天我们再来分析下Android7.0 的phone的来电流程 1.1TelephonyFramework 当有 ...

  8. Android7.0 Phone应用源码分析(一) phone拨号流程分析

    1.1 dialer拨号 拨号盘点击拨号DialpadFragment的onClick方法会被调用 public void onClick(View view) { int resId = view. ...

  9. Solr4.8.0源码分析(25)之SolrCloud的Split流程

    Solr4.8.0源码分析(25)之SolrCloud的Split流程(一) 题记:昨天有位网友问我SolrCloud的split的机制是如何的,这个还真不知道,所以今天抽空去看了Split的原理,大 ...

随机推荐

  1. MySQL忘记密码怎么办-MySQL修改密码(亲测可用)

    前言: 最近要用到本地的MySQL,结果把密码忘记了. ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using pas ...

  2. buu signin

    一.拖入ida,来静态分析F5大法好 要注意的点: 1._gmz_init_set_str() 这个函数,也是看师傅的wp,学到的,以后还是得多google, 本质上是这个函数: int mpz_in ...

  3. 整理C#获取日期显示格式

    C#获取当前日期的几种显示格式 有时候需要用一些不常用的日期格式时,总是要去网上查找,很多都是复制粘贴,还不完整.就整理一下. DatetimeTextBox.Text += DateTime.Now ...

  4. C# 8.0和.NET Core 3.0高级编程 分享笔记二:编程基础第一部分

    基础部分被我分为了2篇,因为实在太多了,但是每一个知识点我都不舍得删除,所以越写越多,这一篇博客整理了4个夜晚,内容有点多建议慢慢看.本章涵盖以下主题: 介绍C# 理解C#的基础知识 使用变量 处理空 ...

  5. Mysql常用语句整理

    把工作常用的mysql命令整理一下,省的用的时候在到处找 1.常用命令 1.1 登录 mysql -u root -p 1.2 生成随机数 若在 i<=R<=j 范围内生成随机数 FLOO ...

  6. NOIP 模拟赛 day5 T2 水 故事题解

    题目描述 有一块矩形土地被划分成 \(\small n×m\) 个正方形小块.这些小块高低不平,每一小块都有自己的高度.水流可以由任意一块地流向周围四个方向的四块地中,但是不能直接流入对角相连的小块中 ...

  7. 从源码角度谈谈MySQL "Too many open files"错误的根本原因

    "Too many open files"是一个比较常见的错误,不仅仅是在 MySQL 中.只要是在 Linux 中启动的进程,都有可能遇到这个错误. 究其原因,是进程打开的文件描 ...

  8. UI自动化测试框架Gauge 碰到无法识别Undefined Steps 红色波纹标记

    如果碰到无法识别的情况,例如下面的红色波纹,可以试一下: 第一步: 第二步: 不勾选'offline work' 第三部:刷新之后可以重新编译.

  9. CSS从入门到喜欢,从喜欢到着魔

    如果把网页比作一个人的话,html就是他的骨架,而css是他的皮肤,javascript是神经控制着行动.html,css,javascript都是构建网页的核心技术. CSS简介 css指的是层叠样 ...

  10. SQL USE语句(选择数据库)

    对于大型的软件系统,会存在多个数据库,用来存储不同的数据,那么我们在开始操作之前,需要选择一个需要操作的数据库,进行后续数据的增.删.改.查工作. SQL USE语句用于选择SQL模式中的任何现有数据 ...