1. 概述
1.1 OpenFeign是什么
Feign是一个声明式的Web服务客户端(Http客户端),让编写WEB服务客户端变得非常容易,只需要创建一个接口并在接口上添加注解即可
HTTP 客户端: 当我们自己的后端项目需要调用别的项目的接口时,就需要通过HTTP客户端来调用,在实际开发过程中经常会遇到这种场景,比如微服务之间调用,
除了微服务之外,可能会涉及到对接一些第三方接口也需要用到HTTP客户端来调用第三方接口。
1.2 OpenFeign能干什么
Java当中常见的HTTP客户端有很多,除了Feign,类是的还有Apache的HttpClient
以及OKHttp3
,还有SpringBoot自带的RestTemplate
,这些都是Java当中常见的HTTP客户端。
所有的客户端相比较:Feign更加简单一点,在Feign的实现下,我们只需要创建一个接口并时用注解的方式来配置它,即可完成对服务提供方的接口绑定。
1.3 OpenFeign和Feign的区别
Feign
Feign是Spring Cloud组件中一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon用来做客户端负载均衡,去调用服务注册中心的服务;
Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用注册中心的服务1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>OpenFeign
OpenFeign是Spring cloud在Feign的基础上支持了Springmvc的注解,如@RequestMapping等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Feign是在2019年就不再更新了,通过Maven网站就可以看出来,随之取代的是OpenFeign,从名字上就可以知道,是Feign的升级版。
1.4 @FeignClient
使用OpenFeign就一定会用到这个注解,@FeignClient属性如下:
name
: 指定该类的容器名称,类似于@Service(容器名称)url
: url一般用于调试,可以手动指定@FeignClient调用的地址decode404
: 当发生HTTP 404错误是,如果该字段为true,会调用decoder进行解码,否则抛出FeignClientExceptionconfiguration
: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contractfallback
: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口fallbackFactory
: 工厂类,用于生成fallback类实例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复代码path
:定义当前FeignClient统一前缀,当我们项目中配置了server.context-path
时使用
下面这两种写法本质没有区别,他们都是将FeignClient注入到Spring容器中
写法1
1
2
3
public interface FeignTestService {
}写法2
1
2
3
4
public interface FeignTestService {
}
远程调用接口当中,一般我们称提供接口的服务为提供者,而调用接口的服务为消费者。而OpeneFeign一定是用在消费者上。
2. OpenFeign使用
2.1 OpenFeign常规远程调用
所谓常规远程调用,指的是对接第三方接口,和第三方并不是微服务模块关系,所以肯定不能通过注册中心来调用服务
- 添加OpenFeign依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency> - 启动类添加
@EnableFeignClients
1
2
3
4
5
6
7
public class ConsumeApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumeApplication.class, args);
}
} - provider提供接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ProviderController {
public String select(int pageId, int pageSize) {
JSONObject jsonpObject = new JSONObject();
jsonpObject.append("pageId", pageId);
jsonpObject.append("pageSize", pageSize);
jsonpObject.append("mesg","查询select接口成功");
return jsonpObject.toString();
}
public String selectByPrice(double price) {
JSONObject jsonpObject = new JSONObject();
jsonpObject.append("price", price);
jsonpObject.append("mesg","查询selectByPrice接口成功");
return jsonpObject.toString();
}
public String create(double price) {
JSONObject jsonpObject = new JSONObject();
jsonpObject.append("price", price);
jsonpObject.append("mesg","create接口成功");
return jsonpObject.toString();
}
} - consume调用provider接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* provider服务设置了context-path=/provider,所以接口上需要加上/provider
* @author xiaoyuge
*/
public interface FeignService {
String select(int pageId, int pageSize) ;
String selectByPrice(double price) ;
String create(double price) ;
} - 消费者添加Controller 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/**
* @author xiaoyuge
*/
public class ExampleController {
private FeignService feignService;
public String select() {
return feignService.select(1, 10);
}
public String selectByPrice() {
return feignService.selectByPrice(50d);
}
public String create() {
return feignService.selectByPrice(40d);
}
} - 测试访问
http://localhost:8081/select
@SpringQueryMap
注解
Spring cloud项目使用feign的时候都会发现一个问题,就是get方式无法解析对象参数,其实feign是支持对象传递的,但是得是Map形式,而且不能为空,与Spring在机制上不兼容,因此无法使用
Spring cloud 2.1x版本提供了 @SpringQueryMap注解,可以传递对象参数,框架自动解析
2.2 OpenFeign微服务使用步骤
微服务之间使用OpenFeign,肯定是要通过注册中心来访问服务的,提供者将自己的IP+端口注册到注册中心,然后对外提供一个服务名称,消费者根据服务名称去注册中心寻找IP+端口
那么对上面的代码进行改造:
- 作为消费者,想要调用提供者服务需要以下内容
消费者想要通过服务名称来调用提供者,那么就一定需要配置注册中心的服务发现功能,假如提供者使用的是Eureka,那么消费者就必须配置Eureka服务发现。
1
2
3
4
5
6
7
8
9
10
11
12
13//PROVIDER-SERVICE 是提供者的服务名称
public interface FeignService {
String select(int pageId, int pageSize) ;
String selectByPrice(double price) ;
String create(double price) ;
} - 提供者的接口,提供者可以是集群
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ProviderController {
private String serverPort;
public String select(int pageId, int pageSize) {
JSONObject jsonpObject = new JSONObject();
jsonpObject.append("pageId", pageId);
jsonpObject.append("pageSize", pageSize);
jsonpObject.append("mesg", "当前端口:" + serverPort);
return jsonpObject.toString();
}
} - 启动注册中心Eureka以及两个服务提供者
访问http://localhost:8081/select
会轮询调用两个服务提供者的接口使用OpenFeign,根据服务名称调用,OpenFeign他本身就集成了ribbon自带负载均衡。
2.3 OpenFeign超时控制
提供方接口,制造超时场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public String select(int pageId, int pageSize) {
try{
//暂停几秒
TimeUnit.SECONDS.sleep(4);
}catch (Exception ex){
ex.printStackTrace();
}
JSONObject jsonpObject = new JSONObject();
jsonpObject.append("pageId", pageId);
jsonpObject.append("pageSize", pageSize);
jsonpObject.append("mesg","当前端口:"+serverPort);
return jsonpObject.toString();
}消费者增加超时设置,并调用该接口
1
2
3
4
5feign:
client:
config:
default:
readTimeout: 3000 #请求超时时间当消费方调用提供方时候,超时时间为3s,超过后报错
在消费者增加如下配置
1
2
3
4
5
6#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000在OpenFeign高版本中,可以默认客户端和命名客户端上配置超时,OpenFeign使用两个参数:
- connectTimeout: 防止由于服务器处理时间长而阻塞调用者
- readTimeout:从连接建立时开始,在返回响应时间过长触发
2.4 OpenFeign 日志打印
Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中HTTP请求的细节。
说白了就是对Feign接口的调用请求进行监控和输出。
日志级别:
NONE:默认的,不显示任何日志
BASIC:仅记录请求方法、URL、响应状态吗和执行时间
HEADERS:除了BASIC中定义的信息之外,还有请求和响应头信息
FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文以及元数据
配置日志Bean:
1 | import feign.Logger; |
Yaml文件需要开启日志的Feign客户端
1 | #设置日志级别 |
后台日志查看:
1 | 2023-07-16 18:03:22.274 DEBUG 70940 --- [nio-8081-exec-1] org.example.FeignService : [FeignService#select] ---> GET http://SLEUTH-PROVIDER/provider/select?pageId=1&pageSize=10 HTTP/1.1 |
2.5 OpenFeign 添加Header
提供了四种方式:
在
@RequestMapping
中添加1
2
3
4
5
6
7
8
9
10
11/**
* @author xiaoyuge
*/
public interface FeignService {
String queryByIds( Long[] ids);
}在方法参数签名添加
@RequestHeader
注解1
2
3
4
5
6
7
8
9
public interface FeignService {
String queryByIds( Long[] ids);
}设置多个属性时,可以使用Map:
1
2
3
4
5
6
7
8
public interface FeignService {
String queryByIds( Long[] ids, MultiValueMap<String, String> headers);
}使用
@Header
注解1
2
3
4
5
6
7
8
public interface FeignService {
String queryByIds( Long[] ids);
}实现RequestInterceptor接口
只要通过FeignClient访问的接口都会走这个拦截器,所以使用的时候需要注意一下
1
2
3
4
5
6
7
public class FeignRequestInterceptor implements RequestInterceptor {
public void apply(RequestTemplate temp) {
temp.header(HttpHeaders.AUTHORIZATION, "XXXXX");
}
}
2.6 手动创建Feign客户端
@FeignClient
无法支持统一个service具有多种不同配置的FeignClient,因此,在必要时需要手动创建FeignClient
@FeignClient(value = “SLEUTH-PROVIDER”)
假如出现两个服务名称为SLEUTH-PROVIDER的FeignClient,项目会启动报错,有时候我们服务之间调用的地方较多,不可能将所有调用都放到一个FeignClient下,这时候就需要自定义来解决这个问题
注意:以下内容都在消费者服务里面添加
官网当中也明确提供了自定义FeignClient,以下是在官网基础上对自定义FeignClient的一个简单封装
首先创建FeignClientConfigure
类,这个类相当于FeignClient
的工具类
1 | import feign.*; |
使用工具类的方法创建多个FeignClient配置
1 |
|
其中,super.buildAuthorizedUserFeignClient()方法中,第一个参数为调用服务的接口类,第二个参数为被调用服务在注册中心的service-id。
1 | //@FeignClient(value = "SLEUTH-PROVIDER") 这里就不需要@FeignClient注解了,通过配置去获取Service-id |
使用的时候正常注入使用即可
1 |
|
2.7 Feign继承支持
Feign通过单继承接口支持样板API,这允许将常用操作分组到方便的基本接口中
- UserService
1
2
3
4public interface UserService {
User getUser(long id) ;
} - UserResource
1
2
3
public class UserResource implements UserService {
} - UserClient
1
2
3
4
public interface UserClient extends UserService {
}
2.8 Feign与Cache集成
如果使用@EnableCaching
注解,CachingCapability则创建并注册一个 bean,以便您的 Feign 客户端识别@Cache*其接口上的注解:
1 | public interface DemoClient { |
您还可以通过 property 禁用该功能feign.cache.enabled=false
注意feign.cache.enabled=false只有在高版本才有
2.9 OAuth2支持
可以通过设置以下标志来启用OAuth2支持:
1 | true = |
当标志为true
并且存在Oauth2客户端上下文资源详细信息时,OAuth2FeignRequestInterceptor
将创建一个Bean.在每个请求之前,拦截器解析所需要的访问令牌并将其作为表头包含在内。
有时,当为Feign客户端启用负载均衡时,如果也希望使用负载均衡来获取访问令牌。为此,需要确保负载均衡器位于类路径(spring-cloud-starter-loadbalancer))上,并通过设置以下标志显式启用OAuth2FeignRequestInterceptor 的负载均衡:
1 | true = |
注意feign.cache.enabled=false只有在高版本才有
版权声明:本文为CSDN博主「怪 咖@」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43888891/article/details/126171740