【SpringCloud技术专题】「Feign」从源码层面让你认识Feign工作流程和运作机制
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。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
ReSharper 2021.3 路线图公布
ReSharper 2021.3 的开发路线图现已公布,包含一系列在 2021.3 发布周期的优先事项列表。不过官方也表示这些都是正在开发中的功能,其中一些可能会延迟到更高版本。列表内容如下: Visual Studio 2022 开始为即将发布的 Visual Studio 主要版本准备 ReSharper,并已经为预览版提供了 ReSharper 的早期版本。 C# 10 支持 ReSharper 将更新代码分析、导航和重构以支持 C# 10 的新功能,包括常量内插字符串、记录结构、列表模式、文件范围的命名空间、全局 using 指令等等。 语法检查 计划通过检查语法错误将 ReSharper集成的拼写检查功能这个功能提升到另一个层次,以进一步提高代码库的质量。 Blazor ReSharper正在努力为 .NET 6 提供 Blazor 支持。 Predictive Debugger ReSharper已经完成了为该功能提供动力的引擎,现在正在研究 UI/UX 选项。 LINQ Visualizer 类似于 IntelliJ IDEA 如何帮助分析 Java 流操作,ReShar...
-
下一篇
写给程序员的JMeter教程(三):一个性能测试的五大基本要素
非常抱歉,本以为这周可以进入实操,发现还是得继续讲一些理念的东西。 当然,我认为有时候学习一个技术,理解理念的东西可能更重要。因为这是『知其然,知其所以然』的一个必须的过程。 如果我们只就技术学习它如何使用,不去思考与理解它背后的一些东西,这样在技术频繁更新的今天,应付技术的更新就会变得非常吃力。 本周,继续写给程序员的JMeter教程系列,这是第四篇,本系列其它文章为: 写给程序员的JMeter教程(序):程序员需要掌握的能力 写给程序员的JMeter教程(一):理解性能测试的常用指标 写给程序员的JMeter教程(二):JMeter与LoadRunner的简要对比 如何构建一个性能测试 想要构建一个性能测试,其中有五大要素是一定得具备的,它们是一个完整的性能测试都不可或缺的一部分。 这五个要素分别是: 服务环境考量 用户模拟 测试业务点 断言 数据与结论 当然,一个性能测试可以设计的更复杂,但就算是最简单的性能测试,比如单个接口的性能测试,都不得不包含这五大要素。 因此,在开始一个性能测试之前,你就得思考这几个要素如何配置与实现。 1. 服务环境考量 我们都知道,服务的部署环境及方...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Hadoop3单机部署,实现最简伪集群
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2全家桶,快速入门学习开发网站教程
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作