OpenFeign的使用
# OpenFeign的使用
# 服务间的调用方式
Spring Cloud中服务之间的调用方式主要有:
- Ribbon + RestTemplate:需要手动配置,负载均衡由Ribbon处理,比较灵活但代码较多。
Feign:声明式接口,集成Ribbon和Hystrix,简化开发。- OpenFeign:OpenFeign是Feign的一个升级版本,在Spring Cloud中被推荐使用以替代原Feign。继承了Feign的所有优点,并进行了增强。
异步消息(如RabbitMQ):通过消息队列解耦,适合异步处理。- gRPC:Spring Cloud 并没有直接支持 gRPC,但可以通过社区维护的第三方库来集成,比如
grpc-spring-boot-starter,属于扩展方式。gRPC是由Google开发的一种高性能、开源的远程过程调用(RPC)框架。 - Spring Cloud Gateway:作为API网关,进行路由和过滤,可能用于外部请求的路由,但内部服务间调用可能还是用前几种。
每种方式都有其适用场景,选择时需根据具体需求考虑性能、易用性、扩展性等因素。
例如,对于简单的服务调用,RestTemplate或OpenFeign可能是较好的选择;而对于需要高性能和跨语言支持的场景,gRPC可能更为合适。Spring Cloud Gateway则通常用于构建微服务架构中的网关层,处理外部请求的分发和安全控制等任务。
# 使用OpenFeign替代 RestTemplate
在Spring Cloud环境中推荐使用OpenFeign替代RestTemplate的原因主要集中在以下几个方面:
- 声明式服务调用
- 简化编码:OpenFeign通过注解的方式定义HTTP请求,使得开发者只需声明接口及方法即可实现远程服务的调用,而不需要手动构建HTTP请求。这种方式大大简化了代码编写过程。
- 提高可读性:相比
RestTemplate,OpenFeign的声明式编程模型让服务调用逻辑更加直观、易于理解。
自动集成Spring MVC注解:OpenFeign支持Spring MVC注解(如
@RequestMapping,@GetMapping等),这使得从传统的Spring MVC控制器迁移到微服务客户端变得非常容易。内置负载均衡:当与Spring Cloud Netflix Ribbon结合使用时(尽管Ribbon已经进入维护模式,但OpenFeign默认集成了负载均衡功能),OpenFeign可以自动利用Eureka提供的服务注册发现机制进行客户端负载均衡,无需额外配置。
强大的扩展能力:OpenFeign允许通过自定义配置类和拦截器来增强其功能,例如添加请求头、处理异常等,提供了高度的灵活性。
支持多种序列化格式:除了JSON,OpenFeign还支持其他数据格式(如XML)作为请求和响应的内容类型,增加了其适用范围。
减少样板代码:使用
RestTemplate时,每次发起HTTP请求都需要创建模板实例并设置相应的参数,而在OpenFeign中,只需要定义一次接口,就可以重复使用这些定义,减少了大量的样板代码。
# 硬编码问题
有人说弃用RestTemplate的一个很大原因,是因为它的硬编码问题,其实不然,下面是它跟nacos进行服务间的调用。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class ConsumerController {
private final RestTemplate restTemplate;
@Autowired
public ConsumerController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@GetMapping("/consumer")
public String consumerService() {
// 注意这里使用的是服务名,而非具体的IP和端口
String result = restTemplate.getForObject("http://provider-service/provider", String.class);
return "Consumer Service Call Result: " + result;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在这个例子中,“provider-service”是注册到Nacos中的服务提供者的名字。RestTemplate会自动通过Nacos发现该服务的所有实例,并根据负载均衡策略选择一个实例进行调用。以上就一定程度上解决了该问题。
但为什么还要弃用它呢?
- 缺乏对复杂负载均衡策略的支持
- 尽管Ribbon提供了基本的负载均衡策略(如轮询、随机等),但对于更复杂的负载均衡需求(例如基于响应时间、流量权重等的自定义策略),可能需要额外的工作来实现或集成第三方库。
- 错误处理与重试机制不足
- 默认情况下,
RestTemplate对于网络异常或服务不可达的情况处理较为简单。虽然可以通过添加拦截器等方式增强其错误处理能力和重试逻辑,但这增加了开发者的负担。 - 对比之下,OpenFeign提供了更加灵活的错误处理机制(如通过实现
ErrorDecoder接口),以及内置的重试功能,使得处理这类问题更为简便。
- 维护性和扩展性
- 使用
RestTemplate进行服务调用时,随着项目规模的增长,手动构建HTTP请求可能会变得冗长且难以维护。尤其是在需要处理多种不同的HTTP方法(GET、POST等)、不同格式的数据转换(JSON、XML等)时,代码量和复杂度都会显著增加。如果服务名很多且很长,那调用时,岂不是要传入很多,很长的参数。 - OpenFeign通过声明式的方式简化了这些操作,减少了模板代码的数量,提高了代码的可读性和维护性。
能不能像我们去调用以前的一个逻辑方法一样,调用我们之前的一个业务方法一样,去调用我们的远程方法呢?比方说我去要用一个stock service点点什么点这个reduct方法,这样直接来请求我的远程方法,我们就可以直接来接收这个返回参数了,这样多简便,对不对?
Feign就实现了这个功能。
# 声明式的服务间调用
使用SpringCloud LoadBalancer+RestTemplate时,利用RestTemplate对http请求的封装处理形成了一套模版化的调用方法。
但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,但这样的调用通常需要编写大量的样板代码,如构建 HTTP 请求、处理响应等。例如,使用 RestTemplate 手动构建请求和处理响应的代码可能非常冗长,而使用 OpenFeign 只需几行注解即可完成相同的功能。
// 传统方式
ResponseEntity<String> response = restTemplate.getForEntity("http://your-service-name/endpoint", String.class);
String result = response.getBody();
2
3
这样的调用数量上来后,会特别繁琐与麻烦。
为了解决这个问题,OpenFeign 出现了, 它通过声明式接口和注解,自动生成这些代码,大大减少了开发工作量。
在OpenFeign的实现下,我们只需创建一个接口并使用注解的方式来配置它(在一个微服务接口上面标注一个@FeignClient注解即可),即可完成对服务提供方的接口绑定,统一对外暴露可以被调用的接口方法,大大简化和降低了调用客户端的开发量,也即由服务提供者给出调用接口清单,消费者直接通过OpenFeign调用即可。
// OpenFeign 方式
@FeignClient(name = "your-service-name")
public interface YourServiceClient {
@GetMapping("/endpoint")
String callService();
}
2
3
4
5
6
# 它的特点
OpenFeign除了简化服务间的调用外,它还有以下几个优势:
集成服务发现和负载均衡:
- 与 Spring Cloud 的服务发现组件和负载均衡组件无缝集成,自动处理服务发现和负载均衡。
- 提高了系统的可维护性和灵活性。
断路器支持:
- 与 Hystrix 集成,提供断路器功能,增强了系统的稳定性和容错能力。
可扩展性:
- 支持自定义编码器、解码器、错误处理器等,满足不同业务场景的需求。
但,在使用OpenFeign的时候请注意——性能开销,这个问题。
- 由于 OpenFeign 是基于注解和接口的动态代理实现,可能会引入一定的性能开销。
- 在高并发场景下,需要注意性能调优。
# 总结
Spring Cloud OpenFeign 是一个强大的声明式 HTTP 客户端,它简化了微服务之间的调用,提供了服务发现、负载均衡和断路器等功能。通过简单的接口定义和注解,开发者可以更加专注于业务逻辑,而无需关心底层的 HTTP 请求细节。
# 注意事项-定义位置
分布式系统中,一般都会有服务的提供者和消费者吗?那Feign接口一般定义在哪一方?是否有要求,还是说随意?
其实定义在哪一方都可以。因为Feign 是一种 声明式 HTTP 客户端,让你用接口方式去调用远程 HTTP 服务。
举个例子:
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/users/{id}")
UserDTO getUser(@PathVariable("id") Long id);
}
2
3
4
5
这段代码在运行时会被 Spring Cloud 动态代理为一个 HTTP 客户端,发请求到名为 user-service 的服务上。
那Feign接口应该定义在哪一方呢?
这是关键问题:
| 方案 | Feign 接口定义位置 | 优点 | 缺点 | 常见场景 |
|---|---|---|---|---|
| ① 定义在消费者侧(Consumer) | 消费者项目中 | 灵活、只依赖自己用到的接口 | 多个消费者容易接口不一致(代码重复、维护困难) | 小型系统、接口不共享的情况 |
| ② 定义在一个独立的“API 通用模块”中 | 单独的模块,比如 user-api | 消费者和提供者都依赖同一份接口定义,避免重复、保持一致 | 提供者、消费者耦合较紧;需要额外维护一个公共模块 | 中大型系统、团队协作项目(推荐) |
| ③ 定义在提供者侧(Provider) | 由 Provider 定义并暴露出来 | 提供者接口权威、可与 Controller 共享 DTO | 如果 Provider 改动接口,Consumer 必须重新依赖新版(升级繁琐) | 内部系统、接口版本稳定的场景 |
为什么推荐“公共 API 模块”方案(②)
这是目前在 Spring Cloud / Dubbo 等分布式架构中 最常见、最推荐 的设计:
新建一个独立模块,比如:
user-api/
├── UserDTO.java
└── UserClient.java // Feign接口定义
2
3
然后:
user-service(提供者)实现这些接口;order-service(消费者)通过依赖这个user-api模块调用。
优点
- 接口与数据模型统一,防止提供者和消费者的接口签名不一致。
- 双方约定清晰(接口契约一目了然)。
- 消费者可独立测试,不必依赖完整的 Provider 部署。
- 减少重复代码。
示例目录结构
user-api/
└── UserClient.java
user-service/
└── 实现UserClient定义的接口
order-service/
└── 依赖user-api并通过Feign调用
2
3
4
5
6
有些人会说。
OpenFeign是声明在我们的这个服务消费端的,也就是在我们的客户端的。
不要把它声明在我们的这个服务提供方了。
Feign接口定义在消费端。其实是不正确的,即使定义在生产者也是可以的。
# 相关资料和文档
SpringCloud OpenFeign官网:
# 快速上手
SpringCloud OpenFeign官网特性与使用说明 (opens new window)
- https://docs.spring.io/spring-cloud-openfeign/reference/
SpringCloud OpenFeign官网首页介绍 (opens new window)
- https://spring.io/projects/spring-cloud-openfeign#learn
# 上手步骤
注意,OpenFeign,需要跟 服务注册组件一起使用,比如consul或者nacos等。
第一步:引入对应pom坐标,官网的对应说明如下:
To include Feign in your project use the starter with group
org.springframework.cloudand artifact idspring-cloud-starter-openfeign.
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
2
3
4
5
6
7
8
9
从Spring Cloud 2020.0.0版本开始,Ribbon(之前用于负载均衡的默认工具)被Spring Cloud LoadBalancer取代。
因此,如果你遇到了"No Feign Client for loadBalancing defined"这样的错误提示,很可能是由于缺少spring-cloud-starter-loadbalancer依赖或未正确启用Spring Cloud LoadBalancer。
同时,你还要注意的是,这个OpenFeign,它是属于spring cloud,所以你要先确定一下有添加spring load的版本管理器。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version> <!-- 根据需要选择合适的版本 -->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2
3
4
5
6
7
8
9
10
11
注意:OpenFeign是在消费端进行使用。
第二步:修改配置文件,让服务注册中心
server:
port: 80
spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
2
3
4
5
6
7
8
9
10
11
12
13
14
第三步:在主类或配置类中添加注解@EnableFeignClients(需要开放接口给其他服务调用的微服务),开启OpenFeign,启用服务间的发现。
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2
3
4
5
6
7
8
9
第四步:将之前的接口进行抽象添加注解,这些接口可以单独放在一个module中,也可以就在原有module中。这一步也成为定义Feign客户端。
// 定义 Feign 客户端
@FeignClient(name = "your-service-name")
//@FeignClient(name = "order" ,path = "/controller-path")
public interface YourServiceClient {
@GetMapping("/endpoint")
String callService();
}
2
3
4
5
6
7
your-service-name,就是你注册到服务中心的微服务名称,下方的接口callService(),就是your-service-name-微服务中对应的接口,请注意接口对应的返回值也要一致。如果controller类上有mapping路径,则需要加上属性 path,值则为 controller-path。
到此,OpenFeign实现服务间的调用全部完成。
OpenFeign,它的这种远程调用的方式,它会自动的帮我们去集成ribbon,帮我们去集成负载均衡器.
同样的会去帮我们集成我们的nacos。根据我们在接口,注解上,写的这个服务名,去我们nacos当中获取对应的所有的服务实例。再结合我们的这个负载均衡器来进行调用,使用了动态代理。这些是基本的原理。
在需要的服务中注入并使用 Feign 客户端(即 定义的接口),即可调用其他微服务接口。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class YourService {
@Autowired
private UserServiceClient userServiceClient;
public User getUserById(Long id) {
return userServiceClient.getUser(id);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
最后提示,为了更好的使用,可以将以上第四步中的接口,放到一个公共module中,从而达到更高效的利用率。
# 原理探究
Feign 接口的位置并不强制限定(它只是接口定义),但要理解“其他服务是怎么找到对应服务实例的”,就必须搞清楚 Feign 调用的完整执行流程。
以下是他在分布式系统中不同服务调用的实例:
OrderService(消费者)
↓
1 调用 Feign 接口代理
↓
2 通过服务名(例如 user-service)从注册中心拉取实例列表
↓
3 通过负载均衡策略选取一个实例
↓
4 构造 HTTP 请求(封装URL、参数、Header)
↓
5 发送 HTTP 请求 → UserService(提供者)
↓
6 提供者处理请求(Controller 层)并返回响应
↓
7 Feign 将响应反序列化为 Java 对象
↓
返回结果给调用方
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
那它的原理又是什么呢?其实就是AOP,动态代理。
OpenFeign 的实现原理主要包括以下几个方面:
- 动态代理
- 注解解析
- HTTP 请求构建和发送
- 以及响应处理。
先大致说下流程:
首先是要通过咱们启动类上的@EnableFeignClients这个注解,开启对Feign接口代理对象的构建以及装配。在注解源码中咱们可以看到它导入了一个叫做FeignClientsregister的注解。这个注解会扫描添加了`@FeignClientd注解的接口,并且创建远程调用的代理对象。而且还会将这个对象注入到咱们的spring容器中。这样我们就可以在需要的位置直接注入rpc远程调用的代理对象。
而在构建代理对象时,会为每一个远程调用的方法生成一个 requestTemplate 的请求模板实例。内部存储了整个请求的路径,请求方式以及请求参数等等。在发生请求调用时,会基于咱们的 requestTemplate 生成一个request的实例。在做具体请求发送前,需要基于FeignClient 的客户端的负载均衡实例去选择合适的服务。那一般会采用ribbon或者是咱们的load baLance,再基于成员变量client的对象的delegate去完成HTTP的请求的提交。而delay gate类型一般咱们会采用Apache 提供的HTTP client去完成远程调用。
# 1. 动态代理
OpenFeign 使用 Java 的动态代理机制来生成接口的实现类。当开发者定义了一个带有 @FeignClient 注解的接口时,OpenFeign 会为这个接口生成一个代理类。这个代理类负责处理实际的 HTTP 请求和响应。
# 2. 注解解析
OpenFeign 通过解析接口上的注解来确定 HTTP 请求的详细信息,包括请求方法、URL、请求参数等。常用的注解包括:
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping等,用于指定 HTTP 方法。@PathVariable、@RequestParam、@RequestBody等,用于指定请求参数的来源和格式。
# 3. HTTP 请求构建和发送
OpenFeign 使用 HttpClient、OkHttp 或 Ribbon 等 HTTP 客户端库来构建和发送 HTTP 请求。具体步骤如下:
- 解析注解:根据接口上的注解解析出请求方法、URL、请求参数等信息。
- 构建请求:使用解析出的信息构建 HTTP 请求。
- 发送请求:通过配置的 HTTP 客户端发送请求。
# 4. 响应处理
OpenFeign 处理 HTTP 响应并将结果转换为接口方法的返回类型。具体步骤如下:
- 接收响应:从 HTTP 客户端接收响应。
- 解析响应:将响应体解析为指定的返回类型。
- 返回结果:将解析后的结果返回给调用者。
# 总结
其他服务调用这些接口,先去OpenFeign中找,OpenFeign通过代理进行请求,得到结果后,返回给调用者。是不是有拦截器那味了。