Feign 和 OpenFeign 的区别?

Sherwin.Wei Lv7

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
2
3
4
5
 <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>

前面说了Feign本身有自己的使用方式,所以可以脱离SpringCloud体系

所以Feign也有类似Spring MVC相关的注解,可以用来指定Http请求的调用路径

如下所示为加了Feign原生的注解的接口

1
2
3
4
5
6
public interface OrderApiClient {

@RequestLine("GET /order/{orderId}")
Order queryOrder(@Param("orderId") Long orderId);

}

有了接口之后,还得为OrderApiClient创建一个对象,需要手动通过Feign.builder()来创建

1
2
3
4
5
6
7
8
9
public class FeignDemo {

public static void main(String[] args) {
OrderApiClient orderApiClient = Feign.builder()
.target(OrderApiClient.class, "http://localhost:8088");
orderApiClient.queryOrder(9527L);
}

}

这就成功创建了OrderApiClient对象,之后就可以通过OrderApiClient对象调用远程服务了

Feign的本质:动态代理 + 七大核心组件

相信稍微了解Feign的小伙伴都知道,Feign底层其实是基于JDK动态代理来的

所以Feign.builder()最终构造的是一个代理对象

Feign在构建动态代理的时候,会去解析方法上的注解和参数

获取Http请求需要用到基本参数以及和这些参数和方法参数的对应关系

当调用动态代理方法的时候,Feign就会将上述解析出来的Http请求基本参数和方法入参组装成一个Http请求

然后发送Http请求,获取响应,再根据响应的内容的类型将响应体的内容转换成对应的类型

这就是Feign的大致原理

5901723468205_.pic.png 在整个Feign动态代理生成和调用过程中,需要依靠Feign的一些核心组件来协调完成

如下图所示是Feign的一些核心组件

image.png

这些核心组件可以通过Feign.builder()进行替换

由于组件很多,这里我挑几个重要的跟大家讲一讲

1、Contract

image.png

前面在说Feign在构建动态代理的时候,会去解析方法上的注解和参数,获取Http请求需要用到基本参数

而这个Contract接口的作用就是用来干解析这件事的

Contract的默认实现是解析Feign自己原生注解的

image.png

SpringCloud在整合Feign的时候,为了让Feign能够识别Spring MVC的注解,所以就自己实现了Contract接口

image.png

2、Encoder

image.png

通过名字也可以看出来,这个其实用来编码的

具体的作用就是将请求体对应的方法参数序列化成字节数组

Feign默认的Encoder实现只支持请求体对应的方法参数类型为String和字节数组

image.png

如果是其它类型就无法使用了

这就导致默认情况下,这个Encoder的实现很难用

于是乎,Spring就实现了Encoder接口

image.png

可以将任意请求体对应的方法参数类型对象序列化成字节数组

3、Decoder

image.png

Decoder的作用恰恰是跟Encoder相反

Encoder是将请求体对应的方法参数序列化成字节数组

而Decoder其实就是将响应体由字节流反序列化成方法返回值类型的对象

Decoder默认情况下跟Encoder的默认情况是一样的,只支持反序列化成字节数组或者是String

image.png

所以,SpringCloud也同样实现了Decoder,扩展它的功能

image.png

4、Client

image.png

从接口方法的参数和返回值其实可以看出,这其实就是动态代理对象最终用来执行Http请求的组件

默认实现就是通过JDK提供的HttpURLConnection来的

image.png

除了这个实现,最最重要的当然是属于它基于负载均衡的实现

如下是OpenFeign用来整合Ribbon实现复杂均衡的核心实现

image.png

5、InvocationHandlerFactory

InvocationHandler我相信大家应该都不陌生

对于JDK动态代理来说,InvocationHandler的invoke方法实现就是动态代理走的核心逻辑

而InvocationHandlerFactory顾名思义其实就是创建InvocationHandler的工厂

image.png

所以,这里就可以猜到,通过InvocationHandlerFactory创建的InvocationHandler应该就是Feign动态代理执行的核心逻辑

InvocationHandlerFactory默认实现是下面这个

image.png

SpringCloud环境下默认也是使用它的这个默认实现

所以,我们直接去看看InvocationHandler的实现类FeignInvocationHandler

image.png

从实现可以看出,除了Object类的一些方法,最终会调用方法对应的MethodHandler的invoke方法

虽然说默认情况下SpringCloud使用是默认实现,最终使用FeignInvocationHandler

但是当其它框架整合SpringCloud生态的时候,为了适配OpenFeign,有时会自己实现InvocationHandler

比如常见的限流熔断框架Hystrix和Sentinel都实现了自己的InvocationHandler

image.png image.png

这样就可以对MethodHandler执行前后,也就是Http接口调用前后进行限流降级等操作。

6、RequestInterceptor

image.png

RequestInterceptor它其实是一个在发送请求前的一个拦截接口

通过这个接口,在发送Http请求之前再对Http请求的内容进行修改

比如我们可以设置一些接口需要的公共参数,如鉴权token之类的

1
2
3
4
5
6
7
8
9
@Component
public class TokenRequestInterceptor implements RequestInterceptor {

@Override
public void apply(RequestTemplate template) {
template.header("token", "token值");
}

}

7、Retryer

image.png

这是一个重试的组件,默认实现如下

image.png

默认情况下,最大重试5次

在SpringCloud下,并没有使用上面那个实现,而使用的是下面这个实现

image.png

所以,SpringCloud下默认是不会进行重试

所以从上面的介绍可以看出,SpringCloud在整个Feign的时候就是重写了很多Feign的组件,让Feign支持SpringCloud相关的生态

Feign核心运行原理分析

上一节说了Feign核心组件,这一节我们来讲一讲Feign核心运行原理,主要分为两部分内容:

  • 动态代理生成原理
  • 一次Feign的Http调用执行过程

1、动态代理生成原理

这里我先把上面的Feign原始使用方式的Demo代码再拿过来

1
2
3
4
5
6
7
8
9
public class FeignDemo {

public static void main(String[] args) {
OrderApiClient orderApiClient = Feign.builder()
.target(OrderApiClient.class, "http://localhost:8088");
orderApiClient.queryOrder(9527L);
}

}

通过Demo可以看出,最后是通过Feign.builder().target(xx)获取到动态代理的

而上述代码执行逻辑如下所示:

image.png 最终会调用ReflectiveFeign的newInstance方法来创建动态代理对象

而ReflectiveFeign内部设置了前面提到的一些核心组件

接下我们来看看newInstance方法

image.png

这个方法主要就干两件事:

第一件事首先解析接口,构建每个方法对应的MethodHandler

MethodHandler在前面讲InvocationHandlerFactory特地提醒过

动态代理(FeignInvocationHandler)最终会调用MethodHandler来处理Feign的一次Http调用

在解析接口的时候,就会用到前面提到的Contract来解析方法参数和注解,生成MethodMetadata,这里我代码我就不贴了

第二件事通过InvocationHandlerFactory创建InvocationHandler

然后再构建出接口的动态代理对象

所以动态代理生成逻辑很简单,总共也没几行代码,画个图来总结一下

5921723468667_.pic.png

2、一次Feign的Http调用执行过程

前面说了,调用接口动态代理的方式时,通过InvocationHandler(FeignInvocationHandler),最终交给MethodHandler的invoke方法来执行

MethodHandler是一个接口,最终会走到它的实现类SynchronousMethodHandler的invoke方法实现

image.png

SynchronousMethodHandler中的属性就是我们前面提到的一些组件

由于整个代码调用执行链路比较长,这里我就不截代码了,有兴趣的可以自己翻翻

不过这里我画了一张图,可以通过这张图来大致分析整个Feign一次Http调用的过程

5931723468744_.pic.png
  • 首先就是前面说的,进入FeignInvocationHandler,找到方法对应的SynchronousMethodHandler,调用invoke方法实现
  • 之后根据MethodMetadata和方法的入参,构造出一个RequestTemplate,RequestTemplate封装了Http请求的参数,在这个过程中,如果有请求体,那么会通过Encoder序列化
  • 然后调用RequestInterceptor,通过RequestInterceptor对RequestTemplate进行拦截扩展,可以对请求数据再进行修改
  • 再然后将RequestTemplate转换成Request,Request其实跟RequestTemplate差不多,也是封装了Http请求的参数
  • 接下来通过Client去根据Request中封装的Http请求参数,发送Http请求,得到响应Response
  • 最后根据Decoder,将响应体反序列化成方法返回值类型对象,返回

这就是Feign一次Http调用的执行过程

Comments