Feign 和 OpenFeign 的区别?
Feign 和 OpenFeign 的区别?
回答重点
Feign 和 OpenFeign 都是用于简化服务之间的 HTTP 调用的工具,让我们可以更加方便地实现服务间的通信。
Feign 最初是由 Netflix 开发的一个声明式 REST 客户端框架,它的目标是让微服务之间的调用像调用本地方法一样容易。
如果我们想调用其它服务的接口,可以创建一个接口,然后通过注解声明所需要调用服务的方法和路径,Feign 可以自动发送 Http 请求和接收响应,转换为方法返回值
而 OpenFeign 是 Spring Cloud 在 Feign 的基础上进一步封装的,它整合了 Spring Cloud 的特性,使得我们可以更加简单地使用 Feign,包括如下几个方面:
- 自动配置:OpenFeign利用Spring Boot的自动配置机制,通过@FeignClient和@EnableFeignClients注解就可以创建一个Feign客户端,极大简化了Feign客户端的创建和配置过程。
- 负载均衡:与Spring Cloud等服务发现组件集成,可以轻松实现客户端负载均衡。
- Hystrix集成:只需要简单的配置就可以快速集成Hystrix,提高系统的容错性。
扩展
原始Feign是什么样的?
一个简单的Demo
方便演示,我使用了SpringCloud的依赖
1 | <dependency> |
前面说了Feign本身有自己的使用方式,所以可以脱离SpringCloud体系
所以Feign也有类似Spring MVC相关的注解,可以用来指定Http请求的调用路径
如下所示为加了Feign原生的注解的接口
1 | public interface OrderApiClient { |
有了接口之后,还得为OrderApiClient创建一个对象,需要手动通过Feign.builder()来创建
1 | public class FeignDemo { |
这就成功创建了OrderApiClient对象,之后就可以通过OrderApiClient对象调用远程服务了
Feign的本质:动态代理 + 七大核心组件
相信稍微了解Feign的小伙伴都知道,Feign底层其实是基于JDK动态代理来的
所以Feign.builder()最终构造的是一个代理对象
Feign在构建动态代理的时候,会去解析方法上的注解和参数
获取Http请求需要用到基本参数以及和这些参数和方法参数的对应关系
当调用动态代理方法的时候,Feign就会将上述解析出来的Http请求基本参数和方法入参组装成一个Http请求
然后发送Http请求,获取响应,再根据响应的内容的类型将响应体的内容转换成对应的类型
这就是Feign的大致原理
在整个Feign动态代理生成和调用过程中,需要依靠Feign的一些核心组件来协调完成
如下图所示是Feign的一些核心组件
这些核心组件可以通过Feign.builder()进行替换
由于组件很多,这里我挑几个重要的跟大家讲一讲
1、Contract
前面在说Feign在构建动态代理的时候,会去解析方法上的注解和参数,获取Http请求需要用到基本参数
而这个Contract接口的作用就是用来干解析这件事的
Contract的默认实现是解析Feign自己原生注解的
SpringCloud在整合Feign的时候,为了让Feign能够识别Spring MVC的注解,所以就自己实现了Contract接口
2、Encoder
通过名字也可以看出来,这个其实用来编码的
具体的作用就是将请求体对应的方法参数序列化成字节数组
Feign默认的Encoder实现只支持请求体对应的方法参数类型为String和字节数组
如果是其它类型就无法使用了
这就导致默认情况下,这个Encoder的实现很难用
于是乎,Spring就实现了Encoder接口
可以将任意请求体对应的方法参数类型对象序列化成字节数组
3、Decoder
Decoder的作用恰恰是跟Encoder相反
Encoder是将请求体对应的方法参数序列化成字节数组
而Decoder其实就是将响应体由字节流反序列化成方法返回值类型的对象
Decoder默认情况下跟Encoder的默认情况是一样的,只支持反序列化成字节数组或者是String
所以,SpringCloud也同样实现了Decoder,扩展它的功能
4、Client
从接口方法的参数和返回值其实可以看出,这其实就是动态代理对象最终用来执行Http请求的组件
默认实现就是通过JDK提供的HttpURLConnection来的
除了这个实现,最最重要的当然是属于它基于负载均衡的实现
如下是OpenFeign用来整合Ribbon实现复杂均衡的核心实现
5、InvocationHandlerFactory
InvocationHandler我相信大家应该都不陌生
对于JDK动态代理来说,InvocationHandler的invoke方法实现就是动态代理走的核心逻辑
而InvocationHandlerFactory顾名思义其实就是创建InvocationHandler的工厂
所以,这里就可以猜到,通过InvocationHandlerFactory创建的InvocationHandler应该就是Feign动态代理执行的核心逻辑
InvocationHandlerFactory默认实现是下面这个
SpringCloud环境下默认也是使用它的这个默认实现
所以,我们直接去看看InvocationHandler的实现类FeignInvocationHandler
从实现可以看出,除了Object类的一些方法,最终会调用方法对应的MethodHandler的invoke方法
虽然说默认情况下SpringCloud使用是默认实现,最终使用FeignInvocationHandler
但是当其它框架整合SpringCloud生态的时候,为了适配OpenFeign,有时会自己实现InvocationHandler
比如常见的限流熔断框架Hystrix和Sentinel都实现了自己的InvocationHandler
这样就可以对MethodHandler执行前后,也就是Http接口调用前后进行限流降级等操作。
6、RequestInterceptor
RequestInterceptor它其实是一个在发送请求前的一个拦截接口
通过这个接口,在发送Http请求之前再对Http请求的内容进行修改
比如我们可以设置一些接口需要的公共参数,如鉴权token之类的
1 |
|
7、Retryer
这是一个重试的组件,默认实现如下
默认情况下,最大重试5次
在SpringCloud下,并没有使用上面那个实现,而使用的是下面这个实现
所以,SpringCloud下默认是不会进行重试
所以从上面的介绍可以看出,SpringCloud在整个Feign的时候就是重写了很多Feign的组件,让Feign支持SpringCloud相关的生态
Feign核心运行原理分析
上一节说了Feign核心组件,这一节我们来讲一讲Feign核心运行原理,主要分为两部分内容:
- 动态代理生成原理
- 一次Feign的Http调用执行过程
1、动态代理生成原理
这里我先把上面的Feign原始使用方式的Demo代码再拿过来
1 | public class FeignDemo { |
通过Demo可以看出,最后是通过Feign.builder().target(xx)获取到动态代理的
而上述代码执行逻辑如下所示:
最终会调用ReflectiveFeign的newInstance方法来创建动态代理对象
而ReflectiveFeign内部设置了前面提到的一些核心组件
接下我们来看看newInstance方法
这个方法主要就干两件事:
第一件事首先解析接口,构建每个方法对应的MethodHandler
MethodHandler在前面讲InvocationHandlerFactory特地提醒过
动态代理(FeignInvocationHandler)最终会调用MethodHandler来处理Feign的一次Http调用
在解析接口的时候,就会用到前面提到的Contract来解析方法参数和注解,生成MethodMetadata,这里我代码我就不贴了
第二件事通过InvocationHandlerFactory创建InvocationHandler
然后再构建出接口的动态代理对象
所以动态代理生成逻辑很简单,总共也没几行代码,画个图来总结一下
2、一次Feign的Http调用执行过程
前面说了,调用接口动态代理的方式时,通过InvocationHandler(FeignInvocationHandler),最终交给MethodHandler的invoke方法来执行
MethodHandler是一个接口,最终会走到它的实现类SynchronousMethodHandler的invoke方法实现
SynchronousMethodHandler中的属性就是我们前面提到的一些组件
由于整个代码调用执行链路比较长,这里我就不截代码了,有兴趣的可以自己翻翻
不过这里我画了一张图,可以通过这张图来大致分析整个Feign一次Http调用的过程
- 首先就是前面说的,进入FeignInvocationHandler,找到方法对应的SynchronousMethodHandler,调用invoke方法实现
- 之后根据MethodMetadata和方法的入参,构造出一个RequestTemplate,RequestTemplate封装了Http请求的参数,在这个过程中,如果有请求体,那么会通过Encoder序列化
- 然后调用RequestInterceptor,通过RequestInterceptor对RequestTemplate进行拦截扩展,可以对请求数据再进行修改
- 再然后将RequestTemplate转换成Request,Request其实跟RequestTemplate差不多,也是封装了Http请求的参数
- 接下来通过Client去根据Request中封装的Http请求参数,发送Http请求,得到响应Response
- 最后根据Decoder,将响应体反序列化成方法返回值类型对象,返回
这就是Feign一次Http调用的执行过程