SpringCloud-技术专区-从源码层面让你认识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。
SpringCloud-技术专区-从源码层面让你认识Feign工作流程和运作机制的更多相关文章
- 【SpringCloud技术专题】「Eureka源码分析」从源码层面让你认识Eureka工作流程和运作机制(上)
前言介绍 了解到了SpringCloud,大家都应该知道注册中心,而对于我们从过去到现在,SpringCloud中用的最多的注册中心就是Eureka了,所以深入Eureka的原理和源码,接下来我们要进 ...
- Netty 源码解析(七): NioEventLoop 工作流程
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第七篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...
- 从RocketMQ的Broker源码层面验证一下这两个点
本篇博客会从源码层面,验证在RocketMQ基础概念剖析,并分析一下Producer的底层源码中提到的结论,分别是: Broker在启动时,会将自己注册到所有的NameServer上 Broker在启 ...
- nginx源码层面探究request_time、upstream_response_time、upstream_connect_time与upstream_header_time指标具体含义
背景概述 最近计划着重分析一下线上各api的HTTP响应耗时情况,检查是否有接口平均耗时.99分位耗时等相关指标过大的情况,了解到nginx统计请求耗时有四个指标:request_time.upstr ...
- Tomcat 调优之从 Linux 内核源码层面看 Tcp backlog
前两天看到一群里在讨论 Tomcat 参数调优,看到不止一个人说通过 accept-count 来配置线程池大小,我笑了笑,看来其实很多人并不太了解我们用的最多的 WebServer Tomcat,这 ...
- Sentinel-Go 源码系列(二)|初始化流程和责任链设计模式
上节中我们知道了 Sentinel-Go 大概能做什么事情,最简单的例子如何跑起来 其实我早就写好了本系列的第二篇,但迟迟没有发布,感觉光初始化流程显得有些单一,于是又补充了责任链模式,二合一,内容显 ...
- Android7.0 Phone应用源码分析(二) phone来电流程分析
接上篇博文:Android7.0 Phone应用源码分析(一) phone拨号流程分析 今天我们再来分析下Android7.0 的phone的来电流程 1.1TelephonyFramework 当有 ...
- Android7.0 Phone应用源码分析(一) phone拨号流程分析
1.1 dialer拨号 拨号盘点击拨号DialpadFragment的onClick方法会被调用 public void onClick(View view) { int resId = view. ...
- Solr4.8.0源码分析(25)之SolrCloud的Split流程
Solr4.8.0源码分析(25)之SolrCloud的Split流程(一) 题记:昨天有位网友问我SolrCloud的split的机制是如何的,这个还真不知道,所以今天抽空去看了Split的原理,大 ...
随机推荐
- Redisson 分布式锁源码 09:RedLock 红锁的故事
前言 RedLock 红锁,是分布式锁中必须要了解的一个概念. 所以本文会先介绍什么是 RedLock,当大家对 RedLock 有一个基本的了解.然后再看 Redisson 中是如何实现 RedLo ...
- 资源:CentOS下载地址资源
新版本系统镜像下载(当前最新是CentOS 7.4版本) CentOS官网 官网地址 http://isoredirect.centos.org/centos/7.4.1708/isos/x86_64 ...
- nginx开启tls1.2及一些注意问题
因为http传输是明文,通过抓包很容易获取到报文, 所以现在很多站点都开启了https,HTTPS在HTTP的基础上加入了SSL协议,对传输的数据进行加密. 目前主流的ssl协议是tlsv1.2 ng ...
- MySQL 插入中文后,显示为空白
https://blog.csdn.net/sun_hj_95/article/details/79488583 在MySQL中插入中文后,显示如下: 解决方案: 在my.ini (在MySQL的安装 ...
- MySQL之group by的使用
使用命令:select 字段 from table_name group by col_name; 注意点: 在有group by的查询语句中,select指定的字段要么就包含在group by语句的 ...
- 十九、.net core使用SoapCore开发webservice接口,以及使用HttpClientFactory动态访问webservice接口
使用SoapCore实现在.net core平台下开发webservice:以及使用HttpClientFactory动态访问webservice. 首先,需要在包项目下面引用SoapCore: 然后 ...
- ROS2学习之旅(20)——创建一个动作消息
本文用来自定义一个动作消息类型. 以下命令用来创建一个工作空间并建立一个功能包: mkdir -p action_ws/src cd action_ws/src ros2 pkg create act ...
- Nginx的安装和部署
Nginx简介 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行.其特点是占有内存少,并发能力强,事实上nginx的并发 ...
- SELECT SQL
替换换行符: update qgnews set article_url=REPLACE(article_url,char(10),'') 替换回车符: update qgnews set artic ...
- 【动画消消乐】HTML+CSS 自定义加载动画 062
效果展示 Demo代码 HTML <!DOCTYPE html> <html lang="en"> <head> <meta charse ...