
SpringCloud声明式调用 - Feign的使用
原创出处 http://www.iocoder.cn/Spring-Cloud/Feign/
本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labs 的 labx-03-spring-cloud-feign 目录。
1. 概述
📖 Feign
Feign 是由 Netflix 开源的声明式的 HTTP 客户端,目前已经捐献给 OpenFeign 社区。
Feign makes writing java http clients easier
通过使用定义简单的接口,并声明 Feign 提供的注解,来实现 HTTP 的调用。示例代码如下:
|
是不是非常方便,我们仅仅创建了 ProductAPI 接口,并使用了 Feign 定义的
@RequestLine
和@Param
注解,就实现了一个获取商品详情的 HTTP API 的调用。
📖 Spring Cloud OpenFeign
Spring Cloud OpenFeign 组件,将 Feign 集成到 Spring Cloud 体系中,实现服务的声明式 HTTP 调用。相比使用 RestTemplate 实现服务的调用,Feign 简化了代码的编写,提高了代码的可读性,大大提升了开发的效率。
Spring Cloud OpenFeign 除了支持 Feign 自带的注解之外,额外提供了对 JAX-RS 注解、SpringMVC 注解的支持。特别是对 SpringMVC 注解的支持,简直是神来之笔,让我们不用学习 Feign 自带的注解,而直接使用超级熟悉的 SpringMVC 注解。
同时,Spring Cloud OpenFeign 进一步将 Feign 和 Ribbon 整合,提供了负载均衡的功能。另外,Feign 自身已经完成和 Hystrix 整合,提供了服务容错的功能。
如此,我们基于注解,极其简单的实现服务的调用,并且具有负载均衡、服务容错的功能。
2. 快速入门
本小节,我们来搭建一个 Spring Cloud OpenFeign 组件的快速入门示例。步骤如下:
首先,搭建一个服务提供者
demo-provider
,启动 2 个实例,注册服务到 Nacos 中。然后,搭建一个服务消费者
demo-consumer
,使用 Ribbon 进行负载均衡,使用 Feign 声明式调用服务提供者demo-provider
的 HTTP 接口。
2.1 搭建服务提供者
创建 labx-03-sc-feign-demo01-provider
项目,作为服务提供者 demo-provider
。最终项目代码如下图所示:
2.1.1 引入依赖
在 pom.xml
文件中,主要引入 Spring Cloud Nacos Discovery 相关依赖。代码如下:
|
具体每个依赖的作用,请看艿艿添加的注释。
2.1.2 配置文件
创建 application.yaml
配置类,添加相应配置项。配置如下:
|
因为稍后我们会启动 2 个服务提供者的实例,所以我们设置 server.port
配置项为 10000 至 19999 之间随机。
2.1.3 ProviderController
创建 ProviderController 类,提供 HTTP 接口。代码如下:
|
2.1.4 DemoProviderApplication
创建 DemoProviderApplication 类,创建应用启动类。代码如下:
|
2.2 搭建服务消费者
创建 labx-01-sca-nacos-discovery-demo01-consumer
项目,作为服务提供者 demo-consumer
。最终项目代码如下图所示:
2.2.1 引入依赖
在 pom.xml
文件中,主要引入 Spring Cloud Nacos Discovery + Spring Cloud Netflix Ribbon + Spring Cloud OpenFeign 相关依赖。代码如下:
|
这里我们没有主动引入 spring-cloud-netflix-ribbon
依赖,因为 spring-cloud-starter-alibaba-nacos-discovery
和 spring-cloud-starter-openfefign
默认都引入了它。如下图所示:
2.2.2 配置文件
创建 application.yaml
配置类,添加相应配置项。配置如下:
|
2.2.3 DemoProviderFeignClient
创建 DemoProviderFeignClient 接口,实现对服务 demo-provider
声明式调用。代码如下:
|
① @FeignClient
注解,声明 Feign 客户端。其中 name
属性,为 Feign 客户端的名字,也为 Ribbon 客户端的名字,也为注册中心的服务的名字。
② 在 #echo(name)
方法上,添加 SpringMVC 注解,实现对 GET /demo
接口的声明式调用。
2.2.4 ConsumerController
创建 ConsumerController 类,提供一个通过 Feign 调用服务提供者的 HTTP 接口。代码如下:
|
2.2.5 DemoConsumerApplication
创建 DemoConsumerApplication 类,创建应用启动类。代码如下:
|
@EnableFeignClients
注解,添加在类上,声明开启 Feign 客户端的功能。
2.3 简单测试
① 通过 DemoProviderApplication 启动 2 次,启动服务提供者的 2 个实例。因为 IDEA 默认同一个程序只允许启动 1 次,所以我们需要配置 DemoProviderApplication 为 Allow parallel run
。如下图所示:
② 通过 DemoConsumerApplication 启动服务消费者。
访问服务消费者的 http://127.0.0.1:28080/hello02?name=wonderx接口 2 次,返回结果如下:
|
使用 Feign 声明式调用服务
demo-provider
成功使用 Ribbon 负载均衡成功
从注册中心 Nacos 加载服务
demo-provider
的服务实例成功
3. 自定义 Feign 配置
本小节,我们来学习如何对 Feign 进行自定义配置。例如说,自定义 Feign 的日志配置,将 Feign 的请求信息打印出来,方便排查问题。
在自定义 Feign 配置的时候,会有全局和客户端两种级别。相比来说,客户端级别是更细粒度的配置。针对每个服务,Spring Cloud OpenFeign 会创建一个 Feign 客户端,并且使用服务名作为 Feign 客户端的名字。
实现 Feign 自定义配置,可以通过配置文件和 Spring JavaConfig 两种方式。
下面,让我们来搭建下自定义 Feign 配置的示例。注意,本小节的所有示例,都是从「2.2 搭建服务消费者」小节的 labx-01-sca-nacos-discovery-demo01-consumer
项目,复制出一个项目,然后在将其配置,自定义 Feign 的日志配置。
在 Feign 中,定义了四种日志级别:
NONE
:不打印日志BASIC
:只打印基本信息,包括请求方法、请求地址、响应状态码、请求时长HEADERS
:在BASIC
基础信息的基础之上,增加请求头、响应头FULL
:打印完整信息,包括请求和响应的所有信息。
3.1 配置文件方式
示例代码对应仓库:
服务提供者:
labx-03-sc-feign-demo01-provider
服务消费者:
labx-03-sc-feign-demo02A-consumer
本小节,我们使用配置文件的方式,实现 Feign 全局和客户端两种级别的自定义配置。
3.1.1 复制项目
复制出 labx-03-sc-feign-demo02A-consumer
项目,进行修改。
3.1.2 配置文件方式
修改 application.yaml
配置文件,额外添加如下配置:
|
① 在 logging.level
配置项下,添加自定义 Feign 接口所在包的日志级别为 DEBUG
。Feign 定义的四种日志级别,针对的是日志内容的级别。最终打印日志时,Feign 是调用日志组件的 DEBUG
级别打印日志,所以这里需要设置为 DEBUG
级别。
② 在 feign.client
配置下,设置 Feign 客户端的配置,对应 FeignClientProperties 配置属性类。其中 config
配置项,可以设置每个 Feign 客户端的配置,并且 key 为 Feign 客户端的名字,value 为 FeignClientConfiguration 对象。
default
为特殊的 key,用于全局级别的配置。logger-level
配置项,设置 Feign 的日志级别。
总结来说,这里配置名字为 demo-provider
的 Feign 客户端的日志级别为 FULL
,全局级别的 Feign 客户端的日志级别为 BASIC
。
3.1.3 简单测试
① 通过「2.1 搭建服务提供者」小节的 DemoProviderApplication 启动,启动服务提供者的实例。
② 通过 DemoConsumerApplication 启动服务消费者。
访问服务消费者的 http://127.0.0.1:28080/hello02?name=wonderx接口,可以看到 IDEA 控制台输出如下 Feign 请求日志:
|
从日志中也可以看出 Feign 是调用日志组件的
DEBUG
级别打印日志。
如果胖友想要测试 Feign 全局级别的自定义配置的效果,可以注释掉 feign.client.config.demo-provider
配置项。
3.1.4 更多配置项
通过 FeignClientConfiguration 配置属性类,我们可以看到配置文件所支持的 FeignClient 的所有配置项。代码如下:
|
3.2 Spring JavaConfig 方式
示例代码对应仓库:
服务提供者:
labx-03-sc-feign-demo01-provider
服务消费者:
labx-03-sc-feign-demo02B-consumer
本小节,我们使用 Spring JavaConfig 的方式,实现 Feign 全局和客户端两种级别的自定义配置。
3.2.1 复制项目
复制出 labx-03-sc-feign-demo02B-consumer
项目,进行修改。最终项目如下图所示:
3.2.2 FeignClient 配置类
创建 DefaultFeignClientConfiguration、DemoProviderFeignClientConfiguration 配置类,FeignClient 配置类。代码如下:
|
① 对于 DefaultFeignClientConfiguration 和 DemoProviderFeignClientConfiguration 两个配置类,我们并没有添加 @Configuration
注解。
因为,Spring Boot 项目默认扫描 DemoConsumerApplication 所在包以及子包下的所有 Bean 们。而 @Configuration
注解也是一种 Bean,也会被扫描到。
如果添加 @Configuration
注解到 DefaultFeignClientConfiguration 和 DemoProviderFeignClientConfiguration 上,将会被 Spring Boot 所扫描到,导致整个项目的 Feign 客户端都使用相同的 Feign 配置,就无法到达 Feign 客户端级别的自定义配置的目的。
因此,我们没有给 DefaultFeignClientConfiguration 和 DemoProviderFeignClientConfiguration 添加 @Configuration
注解。
友情提示,可以不看。
当然,如果胖友想要添加
@Configuration
注解到 DefaultFeignClientConfiguration 和 DemoProviderFeignClientConfiguration 上的话,还有一个不是很推荐的方案,将 DefaultFeignClientConfiguration 和 DemoProviderFeignClientConfiguration 移到和 DemoConsumerApplication 不同包中,避免被 Spring Boot 所扫描到。例如说,在根路径下创建
feign
包,并将 DefaultFeignClientConfiguration 和 DemoProviderFeignClientConfiguration 放入其中。
② 为了避免多个 Feign 客户端级别的配置类创建的 Bean 之间互相冲突,Spring Cloud OpenFeign 通过 FeignContext 类,为每一个 Feign 客户端创建一个 Spring 子上下文。在 Spring Cloud OpenFeign 的设计中,Spring 的上下文设计特别有趣,胖友可以先暂时记住:
全局级别的 FeignClient 配置类是在 Spring 父上下文生效
客户端级别的 FeignClient 配置类在 Spring 子上下文生效。
不过这里要注意,因为 DefaultFeignClientConfiguration 和 DemoProviderFeignClientConfiguration 都创建了 Logger.Level Bean,而 DefaultFeignClientConfiguration 是在 Spring 父上下文生效,会和 DemoProviderFeignClientConfiguration 所在的 Spring 子上下文共享。
这样就导致从 Spring 获取 Logger.Level Bean 时,存在两个而不知道选择哪一个。因此,我们声明 DefaultFeignClientConfiguration 创建的 Logger.Level Bean 为 @Primary
,优先使用它。
友情提示:这里会有一点点绕,胖友好好理解哈~
3.2.3 客户端级别的自定义配置
通过 @FeignClient
注解的 configuration
属性,我们可以设置指定 FeignClient 使用的配置类,即 Feign 客户端级别的自定义配置。因此我们修改 DemoProviderFeignClient 代码如下图所示:
3.2.4 全局级别的自定义配置
通过 @EnableFeignClients
注解的 defaultConfiguration
属性,我们可以设置默认 FeignClient 使用的配置类,即 Feign 全局级别的自定义配置。因此我们修改 DemoConsumerApplication 代码如下图所示:
3.2.5 简单测试
① 通过「2.1 搭建服务提供者」小节的 DemoProviderApplication 启动服务提供者。
② 通过 DemoConsumerApplication 启动服务消费者。
访问服务消费者的 http://127.0.0.1:28080/hello02?name=wonderx 接口,可以看到 IDEA 控制台输出如下 Feign 请求日志:
|
从日志中也可以看出 Feign 是调用日志组件的
DEBUG
级别打印日志。
如果胖友想要测试 Feign 全局级别的自定义配置的效果,可以去掉 DemoProviderFeignClient 类上的 @FeignClient
注解的 configuration
属性。
3.2.6 更多配置 Bean
通过 FeignClientsConfiguration 配置类,我们可以看到 Spring JavaConfig 所支持的 FeignClient 的所有 Bean。例如说:
|
因为 FeignClientsConfiguration 创建的 Bean 基本都有 @ConditionalOnMissingBean
条件注解,所以我们可以通过 Spring JavaConfig 自定义。
3.3 实践建议
对于 Feign 自定义配置,推荐使用配置文件的方式,简单方便好管理。在配置文件的方式无法满足的情况下,使用 Spring JavaConfig 的方式作为补充。不过绝大多数场景下,都基本不需要哈~
配置文件方式的优先级高于 Spring JavaConfig 方式,客户端级别的优先级高于全局级别
4. 继承特性
示例代码对应仓库:
服务提供者 API 项目:
labx-03-sc-feign-demo03-provider-api
服务提供者:
labx-03-sc-feign-demo03-provider
服务消费者:
labx-03-sc-feign-demo03-consumer
Spring Cloud OpenFeign 提供了 SpringMVC 注解的支持,所以我们可以将服务提供者 Controller 提取出一个接口,让服务提供者和消费者共同使用,如下图所示:
这就是 Spring Cloud OpenFeign 提供的继承特性。下面,我们来对「2. 快速入门」提供的示例代码进行改造。
4.1 搭建服务提供者 API 项目
创建 labx-03-sc-feign-demo03-provider-api
项目,提供服务提供者的 API 接口和 DTO 类。最终项目如下图所示:
api
包:提供服务提供者的 API 接口。dto
包:提供数据传输对象 DTO 类。
友情提示:不了解 DTO的胖友,可以看看《浅析 VO、DTO、DO、PO的概念、区别和用处》文章。
4.1.1 引入依赖
在 pom.xml
文件中,主要引入 SpringMVC 相关依赖。代码如下:
|
因为我们需要 SpringMVC 的注解,所以引入 spring-web
依赖。
4.1.2 TestService
创建 TestService 接口,作为服务 user-provider
的 TestController 的 API 接口。代码如下:
|
4.2 搭建服务提供者
本小节,我们将在服务提供者项目中,引入服务提供者 API 项目。
4.2.1 复制项目
从「2.1 搭建服务提供者」小节的 labx-03-sc-feign-demo01-provider
项目,复制出 labx-03-sc-feign-demo03-provider
项目。然后在其上进行修改,方便搭建~
4.2.2 引入依赖
修改 pom.xml
,额外引入服务提供者 API 项目。代码如下:
|
4.2.3 ProviderController
修改 ProviderController 类,实现 TestService 接口。结果如下图所示:
4.3 搭建服务消费者
本小节,我们将在服务消费者项目中,引入服务提供者 API 项目。
4.3.1 复制项目
从「2.2 搭建服务消费者」小节的 labx-03-sc-feign-demo01-consumer
项目,复制出 labx-03-sc-feign-demo03-consumer
项目。然后在其上进行修改,方便搭建~
4.3.2 引入依赖
修改 pom.xml
,额外引入服务提供者 API 项目。代码如下:
|
4.3.3 DemoProviderFeignClient
修改 DemoProviderFeignClient 接口,继承 TestService 接口。结果如下图所示:
4.4 简单测试
① 通过 DemoProviderApplication 启动服务提供者。
② 通过 DemoConsumerApplication 启动服务消费者。
访问服务消费者的 http://127.0.0.1:28080/hello02?name=wonderx 接口,返回结果为 consumer:16445-provider:wonderx
。能够调通 HTTP 接口,说明使用继承特性成功。
4.5 实践建议
在 Spring Cloud OpenFeign 官方文档有这么一段话:
FROM Feign Inheritance Support
It is generally not advisable to share an interface between a server and a client. It introduces tight coupling, and also actually doesn’t work with Spring MVC in its current form (method parameter mapping is not inherited).
意思是不推荐使用继承特性,因为通过 Java 接口的共享,导致服务提供者和消费者的耦合,而微服务的目的是为了服务提供者和消费者的解耦,存在一定的冲突。
不过实际场景下,蛮多公司采用继承特性,显而易见的好处,可以方便服务消费者的快速接入,基本无需编写额外的代码。
具体怎么选择,胖友可以自己进行评估,看看使用继承特性的情况下,在享受优点的同时,是否能够接受带来的缺点。
艿艿个人意见的话,是支持采用继承特性。
从 Dubbo 的使用方式来说,也可以认为它是是支持采用继承特性。
5. 复杂参数
示例代码对应仓库:
服务提供者:
labx-03-sc-feign-demo04-provider
服务消费者:
labx-03-sc-feign-demo04-consumer
在「2. 快速入门」小节中,考虑到简单上手 Spring Cloud OpenFeign 的使用,我们只提供了 Feign 请求单个参数的简单参数例子。但是实际项目中,我们必然会面临传递多个参数的复杂参数的场景。例如说:
|
针对 GET
和 POST
类型的请求,Spring Cloud OpenFeign 传递复杂参数有不同的处理方式。下面,让我们来搭建下复杂参数的示例。
5.1 搭建服务提供者
5.1.1 复制项目
从「2.1 搭建服务提供者」小节的 labx-03-sc-feign-demo01-provider
项目,复制出 labx-03-sc-feign-demo04-provider
项目。然后在其上进行修改,方便搭建~
5.1.2 DemoDTO
创建 DemoDTO 类,示例 DTO 类。代码如此:
|
5.1.3 ProviderController
修改 ProviderController 类,额外增加 GET
、POST
类型请求的复杂参数的示例接口。代码如下:
|
5.2 搭建服务消费者
5.2.1 复制项目
从「2.2 搭建服务消费者」小节的 labx-03-sc-feign-demo01-consumer
项目,复制出 labx-03-sc-feign-demo04-consumer
项目。然后在其上进行修改,方便搭建~
5.2.2 DemoDTO
创建 DemoDTO 类,示例 DTO 类。代码如此:
|
5.2.3 DemoProviderFeignClient
修改 DemoProviderFeignClient 接口,额外增加对「5.1.3 ProviderController」新提供的 HTTP 接口的调用。代码如下:
|
📚 GET
场景
①【最推荐】方式一,采用 Spring Cloud OpenFeign 提供的 @SpringQueryMap
注解,并使用 DemoDTO 对象。
默认情况下,Feign 针对 POJO 类型的参数,即使我们声明为 GET
类型的请求,也会自动转换成 POST
类型的请求。如果我们去掉 @SpringQueryMap
注解,就会报如下异常:
|
Feign 自动转换成了
POST /get_demo
请求,而服务提供者提供的/get_demo
只支持GET
类型,因此返回响应状态码为 405 的错误。
@SpringQueryMap
注解的作用,相当于 Feign 的 @QueryMap
注解,将 POJO 对象转换成 QueryString。
②【较推荐】方式二,采用 SpringMVC 提供的 @RequestParam
注解,并将所有参数平铺开。
参数较少的时候,可以采用这种方式。如果参数过多的话,还是采用方式一更优。
③【不推荐】方式三,采用 SpringMVC 提供的 @RequestParam
注解,并使用 Map 对象。非常不推荐,因为可读性差,都不知道传递什么参数。
📚 POST
场景
① 唯一方式,采用 SpringMVC 提供的 @RequestBody
注解,并使用 DemoDTO 对象。
5.3 简单测试
① 通过 DemoProviderApplication 启动服务提供者。
② 通过 DemoConsumerApplication 启动服务消费者。访问服务消费者的如下接口,进行测试:
5.4 拓展知识
场景 | 文章 |
---|---|
文件上传 | |
Form 表单提交 |
6. Feign 单独使用
示例代码对应仓库:
服务消费者:
labx-03-sc-feign-demo05-consumer
在使用 Spring Cloud 的项目中,我们大多数是通过 Feign 调用从 Ribbon 负载均衡选择的服务实例,而 Ribbon 是通过注册中心获取到的服务实例列表。但是有些场景下,可能想要单独使用 Feign 调用,例如说:
调用第三方服务,例如说短信云服务、推送云服务
调用的虽然是内部服务,但是并没有注册到注册中心,而是使用 Nginx 代理并负载均衡实现高可用
下面,让我们来搭建下 Feign 单独使用的示例。
6.1 复制项目
从「2.2 搭建服务消费者」小节的 labx-03-sc-feign-demo01-consumer
项目,复制出 labx-03-sc-feign-demo05-consumer
项目。然后在其上进行修改,方便搭建~
6.2 DemoProviderFeignClient
修改 DemoProviderFeignClient 接口,改成调用 http://www.iocoder.cn。代码如下:
|
将 @FeignClient
注解的 url
属性设置要调用的服务的地址。不过要注意,保持 name
属性和 url
属性的 host 是一致的,不然还是会使用 Ribbon 进行负载均衡。
6.3 简单测试
通过 DemoConsumerApplication 启动服务消费者。然后访问服务消费者的 http://127.0.0.1:28080/hello02 接口,返回结果如下图表示成功:
7. HTTP 客户端
默认情况下,Feign 通过 JDK 自带的 HttpURLConnection 封装了 Client.Default,实现 HTTP 调用的客户端。因为 HttpURLConnection 缺少对 HTTP 连接池的支持,所以性能较低,在并发到达一定量级后基本会出现。
因此 Feign 提供了另外两个 HTTP 客户端:
OkHttpClient,基于 OkHttp 封装
7.1 使用 Apache HttpClient
示例代码对应仓库:
服务提供者:
labx-03-sc-feign-demo01-provider
服务消费者:
labx-03-sc-feign-demo06A-consumer
本小节,我们来搭建下 Apache HttpClient 的使用示例。
7.1.1 复制项目
从「2.2 搭建服务消费者」小节的 labx-03-sc-feign-demo01-consumer
项目,复制出 labx-03-sc-feign-demo06A-consumer
项目。然后在其上进行修改,方便搭建~
7.1.2 引入依赖
修改 pom.xml
,额外引入 feign-httpclient
依赖。代码如下:
|
7.1.3 配置文件
修改 application.yaml
配置文件,额外添加如下配置:
|
通过 feign.httpclient
配置项,我们可以开启 Feign Apache HttpClient,并进行自定义配置。在 FeignHttpClientProperties 配置属性类中,还有其它配置项,胖友可以简单看看。
不过有一点要注意,虽然说 feign.httpclient.enable
默认为 true
开启,但是还是需要引入 feign-httpclient
依赖,才能创建 ApacheHttpClient 对象。
7.1.4 简单测试
① 在 HttpClientFeignLoadBalancedConfiguration 配置类中打个断点,用于确认是否创建了 ApacheHttpClient 对象。
② 使用 Debug 来启动 DemoConsumerApplication 服务消费者。会进入 HttpClientFeignLoadBalancedConfiguration 打的断点,如下图所示:
7.2 使用 OkHttpClient
示例代码对应仓库:
服务提供者:
labx-03-sc-feign-demo01-provider
服务消费者:
labx-03-sc-feign-demo06B-consumer
本小节,我们来搭建下 OkHttpClient 的使用示例。
7.2.1 复制项目
从「2.2 搭建服务消费者」小节的 labx-03-sc-feign-demo01-consumer
项目,复制出 labx-03-sc-feign-demo06B-consumer
项目。然后在其上进行修改,方便搭建~
7.2.2 引入依赖
修改 pom.xml
,额外引入 feign-okhttp
依赖。代码如下:
|
7.2.3 配置文件
修改 application.yaml
配置文件,额外添加如下配置:
|
通过设置 feign.okhttp.enabled
配置项为 true
,我们可以开启 Feign OkHttp。目前暂无其它 feign.okhttp
配置项。
另外,因为 feign.httpclient.enabled
配置项默认为 true
,所以需要手动设置成 false
,避免使用了 Feign Apache HttpClient。
7.2.4 简单测试
① 在 OkHttpFeignLoadBalancedConfiguration 配置类中打个断点,用于确认是否创建了 ApacheHttpClient 对象。
② 使用 Debug 来启动 DemoConsumerApplication 服务消费者。会进入 OkHttpFeignLoadBalancedConfiguration 打的断点,如下图所示:
7.3 实践建议
OkHttp 和 Apache HttpClient 在性能方面是基本接近的,有资料说 OkHttp 好一些,也有资料说 HttpClient 好一些。艿艿建议的话,按照自己对哪一个更熟悉一点,就选择哪一个。
这里有一篇两者对比的文章《HTTP 客户端连接,选择 HttpClient 还是OkHttp?》,感兴趣的胖友可以阅读一波。
8. 请求重试
示例代码对应仓库:
服务提供者:
labx-03-sc-feign-demo01-provider
服务消费者:
labx-03-sc-feign-demo07-consumer
Feign 和 Ribbon 都有请求重试的功能,两者都启用该功能的话,会产生冲突的问题。因此,有且只能启动一个的重试。目前比较推荐的是使用 Ribbon 来提供重试,如下是来自 Spring Cloud 开发者的说法:
FROM https://github.com/spring-cloud/spring-cloud-netflix/issues/467
在 Spring Cloud OpenFeign 中,默认创建的是 NEVER_RETRY 不进行重试。如此,我们只需要配置 Ribbon 的重试功能即可。
下面,让我们来搭建下 Feign + Ribbon 请求重试的使用示例。
8.1 复制项目
从「2.2 搭建服务消费者」小节的 labx-03-sc-feign-demo01-consumer
项目,复制出 labx-03-sc-feign-demo07-consumer
项目。然后在其上进行修改,方便搭建~
8.2 配置文件
修改 application.yaml
配置文件,额外添加如下配置:
|
关于每个配置项的解释,胖友可以阅读《芋道 Spring Cloud Netflix 负载均衡 Ribbon 入门》的「9. 请求重试」小节。
这里我们设置 ribbon.ReadTimeout
配置项为 1 毫秒,方便模拟请求超时后进行请求重试。
8.3 简单测试
① 通过「2.1 搭建服务提供者」小节的 DemoProviderApplication 启动 2 次,启动服务提供者的 2 个实例。
② 通过 DemoConsumerApplication 启动服务消费者。
访问服务消费者的 http://127.0.0.1:28080/hello02?name=wonderx 接口,返回结果如下:
|
符合预期,多次重试后依然失败。
打开 DemoProviderApplication 控制台,可以看到两个服务都被调用的示例:
|
9. Feign 与 RestTemplate 的对比
从开发效率、可维护性的角度来说,Feign 更加有优势。
从执行性能、灵活性的角度来说,RestTemplate 更加有优势。
个人推荐使用 Feign 为主,RestTemplate 为辅:
相比来说,开发效率、可维护性非常重要,要保证开发的体验。
执行性能的问题,因为 Feign 多一层 JDK 动态代理,所以会差一些。不过 HTTP 调用的整体性能的大头在网络传输和服务端的执行时间,所以 Feign 和 RestTemplate 的性能差距可以相对忽略。
灵活性的问题,99.99% 的情况下,Feign 都能够实现或者相对绕的实现;无法实现的情况下,在考虑采用 RestTemplate 进行实现。
10. Feign 主要组件
绝大多数情况下,我们并不需要去深入了解 Feign 的主要组件。所以本小节的内容,更多作为拓展知识,胖友可以根据自己的需要进行阅读。
10.1 Feign.Builder
Feign.Builder 类,Feign 构造器,可以设置各种配置,最终构建出指定 API 接口的 HTTP “客户端”。示例代码如下:
|
10.2 Client
Client 接口,定义提交 HTTP 请求的方法。
Feign 提供了 4 个 Client 实现类,如下图所示:
Client.Default,基于 JDK HttpURLConnection 封装的 HTTP 客户端。
Client.Proxied,在 Client.Default 的基础上,允许使用
java.net.Proxy
代理。ApacheHttpClient,基于 Apache HttpClient 封装的 HTTP 客户端。
OkHttpClient,基于 OkHttp 封装的 HTTP 客户端。
Spring Cloud OpenFeign 提供了上图另外 2 个 Client 实现类。
LoadBalancerFeignClient,对 Ribbon 进行集成,提供负载均衡的能力。
FeignBlockingLoadBalancerClient,对 Spring Cloud LoadBalancer 的 BlockingLoadBalancerClient 进行集成,提供负载均衡的能力。
这里有一个非常有趣的设计,以 LoadBalancerFeignClient 举例子,我们来看看 Feign 和 Ribbon 是怎么集成的。如下图所示:
胖友按照艿艿在图中写的 ①②③④ 的顺序,进行这段逻辑的理解。本质就是 Feign ->
Ribbon ->
Fegin 的过程。如果不理解的胖友,可以在 LoadBalancerFeignClient 中打个断点,进行调试一波。
10.3 Contract
Contract 接口,契约,负责解析 API 接口的方法元数据,例如说注解、方法参数、方法返回类型等等。
Feign 提供了 3 个 Contract 实现类,如下图所示:
Contract.Default,实现对 Feign 内置的注解的支持。
HystrixDelegatingContract,实现对 Hystrix 的集成,主要增加对方法的返回类型为 HystrixCommand、Observable 等等时的泛型解析,从而获取到真正的返回类型。
Spring Cloud OpenFeign 提供了 SpringMvcContract 实现类,实现了对 SpringMVC 提供的注解的支持。
10.4 Encoder
Encoder 接口,编码器,负责将一个对象转换成 HTTP 请求体。
Feign 和 Spring Cloud OpenFeign 提供了 5 个 Encoder 实现类,如下图所示:
10.5 Decoder
Decoder 接口,编码器,负责将 HTTP 响应转换成一个对象。
Feign 和 Spring Cloud OpenFeign 提供了 8 个 Decoder 实现类,如下图所示:Decoder 类图
10.6 RequestInterceptor
RequestInterceptor 接口,请求拦截器,负责对请求进行拦截,实现自定义的通用逻辑。
Feign 提供了 1 个 RequestInterceptor 实现类,如下图所示:
BasicAuthRequestInterceptor,实现 HTTP Basic Authentication,将账号和密码编码后添加到请求头
Authorization
中。
Spring Cloud OpenFeign 提供了 2 个 RequestInterceptor 实现类。
FeignAcceptGzipEncodingInterceptor,添加
gzip, deflate
到请求头Accept-Encoding
中,表示支持响应结果进行 GZIP 压缩。FeignContentGzipEncodingInterceptor,当 HTTP 请求体的大小超过到达阀值,进行 GZIP 压缩。
10.7 Logger
Logger 抽象类,日志记录器,负责请求信息的日志打印。
Feign 提供了 4 种 Logger 实现类,如下图所示:
10.8 小结
Feign 的原理与源码并不复杂,建议胖友可以阅读下《Spring Cloud Feign 设计原理》 文章,重点要理解两点:
Feign 是如何给 Java API 接口创建动态代理,从而生成调用远程 HTTP API 接口的实现类。
Feign 和 Ribbon 是如何集成的,并实现前者负责 HTTP 接口的声明与调用,后者负责服务实例的负载均衡。