【SpringCloud 技术专题】「Feign 技术专区」从源码层面让你认识 Feign 工作流程和运作机制
Feign 工作流程源码解析
什么是 feign:一款基于注解和动态代理的声明式 restful http 客户端。
原理
Feign 发送请求实现原理
微服务启动类上标记 @EnableFeignClients 注解,然后 Feign 接口上标记 @FeignClient 注解。@FeignClient 注解有几个参数需要配置,这里不再赘述,都很简单。
Feign 框架会扫描注解,然后通过 Feign 类来处理注解,并最终生成一个 Feign 对象。
解析 @FeignClient 注解,生成 MethodHandler
具体的解析类是 ParseHandlerByName。这个类是 ReflectiveFeign 的内部类。
拿到注解元数据以后,循环处理注解元数据,创建每个方法对应的 MethodHandler,这个 MethodHandler 最终会被代理对象调用。最终 MethodHandler 都会保存到下面这个集合中,然后返回。
解析完成以后,调用 ReflectiveFeign.newInstance()生成代理类。
MethodHandler 是 feign 的一个接口,这个接口的 invoke 方法,是动态代理调用者 InvocationHandler 的 invoke()方法最终调用的方法。
重新表述一遍:InvocationHandler 的 invoke()方法最终回调 MethodHandler 的 invoke()来发送 http 请求。这就是 Feign 动态代理的具体实现。
ReflectiveFeign 类的 newInstance()方法的第 57 行:
InvocationHandler.invoke()的具体实现在 FeignInvocationHandler.invoke(),FeignInvocationHandler 也是 ReflectiveFeign 的一个内部类。里面有很多细节处理这里不再赘述,我们直接进入核心那一行代码,以免影响思路,我们是理 Feign 的实现原理的!不要在意这些细节!
// InvocationHandler 的 invoke()方法最终回调 MethodHandler 的 invoke()来发送 http 请求
ReflectiveFeign 类的 invoke()方法,第 323 行,代码的后半段,如下:
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 模板需要经过一系列拦截器的处理,主要有以下拦截器:
BasicAuthRequestInterceptor:授权拦截器,主要是设置请求头的 Authorization 信息,这里是 base64 转码后的用户名和密码。
FeignAcceptGzipEncodingInterceptor:编码类型拦截器,主要是设置请求头的 Accept-Encoding 信息,默认值{gzip, deflate}。
FeignContextGzipEncodingInterceptor:压缩格式拦截器,该拦截器会判断请求头中 Context-Length 属性的值,是否大于请求内容的最大长度,如果超过最大长度 2048,则设置请求头的 Context-Encoding 信息,默认值{gzip, deflate}。注意,这里的 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
这里的 Options 定义了 2 个参数:
connectTimeoutMillis:连接超时时间,默认 10 秒。
readTimeoutMillis:读取数据超时时间,默认 60 秒。
这种方式是最简单的实现,但是不支持负载均衡,Spring Cloud 整合了 Feign 和 Ribbon,所以自然会把 Feign 和 Ribbon 结合起来使用。也就是说,Feign 发送请求前,会先把请求再经过一层包装,包装成 RibbonRequest。
也就是发送请求的另一种实现 LoadBalancerFeignClient。
以负载均衡的方式发送请求
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 行代码:
处理 http 相应
http 请求经过上面一系列的转发以后,最终还会回到 SynchronousMethodHandler,然后 SynchronousMethodHandler 会进行一系列的处理,然后响应到浏览器。
注册 Feign 客户端 bean 到 IOC 容器
查看 Feign 框架源代码,我们可以发现,FeignClientsRegistar 的 registerFeignClients()方法完成了 feign 相关 bean 的注册。
Feign 架构图
第一步:基于 JDK 动态代理生成代理类。
第二步:根据接口类的注解声明规则,解析出底层 MethodHandler
第三步:基于 RequestBean 动态生成 request。
第四步:Encoder 将 bean 包装成请求。
第五步:拦截器负责对请求和返回进行装饰处理。
第六步:日志记录。
第七步:基于重试器发送 http 请求,支持不同的 http 框架,默认使用的是 HttpUrlConnection。
版权声明: 本文为 InfoQ 作者【李浩宇/Alex】的原创文章。
原文链接:【http://xie.infoq.cn/article/e24c9fc8f990aeebf00a3883d】。文章转载请联系作者。
评论