网站首页 > 技术文章 正文
SpringBoot与OpenFeign的整合为构建微服务架构提供了一种强大且灵活的方式。通过声明式的API调用,开发者可以专注于业务逻辑的实现,而不必担心底层的网络通信细节。结合Spring Cloud生态中的其他组件,可以进一步增强系统的可伸缩性、可靠性和安全性。
与OpenFeign的整合的好处
简化HTTP客户端开发
- 声明式编程:通过注解的方式定义HTTP客户端接口,使得代码更加简洁和易于理解。
- 减少样板代码:无需手动编写底层的HTTP请求代码,减少了重复的工作量。
集成Spring Cloud生态
- 服务发现:结合Spring Cloud Eureka或Consul等服务注册中心,可以通过服务名称自动发现并调用相应的服务实例。
- 负载均衡:内置支持Ribbon或其他负载均衡策略,确保请求均匀分布到各个服务实例。
- 熔断机制:结合Hystrix或Resilience4j等库,可以轻松实现服务间的熔断保护。
强大的配置能力
- 全局配置:可以通过配置文件统一管理Feign客户端的行为,如超时设置、重试机制等。
- 自定义配置:可以通过configuration属性指定自定义的配置类,覆盖默认行为。
易于测试
- Mocking:可以很容易地使用Mockito等工具对Feign客户端进行单元测试,提高代码的健壮性。
- 集成测试:通过SpringBootTest框架,可以方便地进行集成测试,验证服务间的通信是否正常。
Feign客户端的注册与初始化
“
当 SpringBoot应用启动时,@EnableFeignClients 注解会触发Feign客户端的扫描和初始化过程。
1. 扫描Feign客户端接口
- SpringBoot在启动过程中会扫描带有 @FeignClient 注解的接口,并将其注册到 Spring 上下文中。
2. 创建FeignContext
- 每个Feign客户端都有一个对应的 FeignContext,用于存储相关的配置信息,如 Encoder、Decoder、Interceptor 等。
3. 解析接口注解
- Feign使用 Contract 接口来解析接口上的注解(如 @GetMapping、@PostMapping 等),并生成元数据。
4. 创建 Target
- Feign使用 Target 接口来表示远程服务的目标。Target 包含了服务名称、URL 和类型等信息。
5. 创建Feign.Builder
- Feign使用 Builder 类来构建Feign客户端实例。Builder 可以配置各种选项,如 Encoder、Decoder、Interceptor 等。
6. 创建Feign.Client
- Feign使用 Client 接口来执行实际的 HTTP 请求。默认情况下,Feign使用 JDK 的 HttpURLConnection,但也可以配置为使用 Apache HttpClient 或 OkHttp。
7. 创建 InvocationHandlerFactory
- Feign使用 InvocationHandlerFactory 来创建动态代理对象的 InvocationHandler,从而实现在方法调用时自动生成 HTTP 请求。
8. 创建动态代理对象
- 最后,Feign使用 Java 动态代理机制,根据 InvocationHandler 创建具体的Feign客户端实例。
关键组件说明
- @EnableFeignClients:启用 Feign客户端扫描。
- @FeignClient:定义 Feign客户端接口。
- FeignContext:存储 Feign客户端的相关配置。
- Targeter:负责创建 Feign客户端实例。
- Contract:解析接口上的注解,生成元数据。
- Encoder/Decoder:处理请求体和响应体的序列化和反序列化。
- Interceptor:拦截 HTTP 请求和响应,进行额外的处理。
- Client:实际执行 HTTP 请求的客户端,如 Apache HttpClient 或 OkHttp。
代码实操
创建Maven多模块项目,父pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>microservices-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>provider-service</module>
<module>consumer-service</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
创建Provider Service
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>microservices-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>provider-service</artifactId>
</project>
Provider Service - application.properties
# 配置服务器端口
server.port=8081
# 日志级别设置为INFO
logging.level.root=INFO
Provider Service - Application
package com.example.providerservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 主应用程序类,用于启动Provider Service
*/
@SpringBootApplication
public class ProviderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderServiceApplication.class, args);
}
}
Provider Service - 数据传输对象(DTO)
package com.example.providerservice.dto;
import lombok.Data;
/**
* 数据传输对象,用于接收和返回请求信息
*/
@Data
public class RequestDto {
private String name;
private int age;
}
/**
* 响应数据传输对象,用于返回处理结果
*/
@Data
public class ResponseDto {
private String message;
private String name;
private int age;
}
Provider Service - 随便搞一个Controller给别人调用
package com.example.providerservice.controller;
import com.example.providerservice.dto.RequestDto;
import com.example.providerservice.dto.ResponseDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 提供者服务的REST控制器
*/
@RestController
@RequestMapping("/api/provider")
@Slf4j
public class ProviderController {
/**
* 处理POST请求的方法
*
* @param requestDto 请求体中的数据
* @return 处理后的响应对象
*/
@PostMapping("/process")
public ResponseDto processRequest(@RequestBody RequestDto requestDto) {
log.info("Received request with name: {} and age: {}", requestDto.getName(), requestDto.getAge());
// 构建响应对象
ResponseDto responseDto = new ResponseDto();
responseDto.setMessage("Processed request for " + requestDto.getName() + " who is " + requestDto.getAge() + " years old.");
responseDto.setName(requestDto.getName());
responseDto.setAge(requestDto.getAge());
return responseDto;
}
}
创建Consumer Service
上面的Provider Service代码都不是重点,随便写一下就可以了。
下面的代码就要认真看了!
Consumer Service - 引入OpenFeign依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>microservices-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>consumer-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Consumer Service - application.properties
# 配置服务器端口
server.port=8082
# 日志级别设置为INFO
logging.level.root=INFO
Consumer Service - Application
加上@EnableFeignClients
package com.example.consumerservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 消费者服务的主应用程序类
*/
@SpringBootApplication
@EnableFeignClients
public class ConsumerServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerServiceApplication.class, args);
}
}
Consumer Service - 重点来了
调用API就是这么简单!
package com.example.consumerservice.client;
import com.example.consumerservice.dto.RequestDto;
import com.example.consumerservice.dto.ResponseDto;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* Feign客户端接口,用于调用Provider Service的API
*/
@FeignClient(name = "providerService", url = "http://localhost:8081")
public interface ProviderClient {
/**
* 调用Provider Service的/process接口
*
* @param requestDto 请求体中的数据
* @return 处理后的响应对象
*/
@PostMapping("/api/provider/process")
ResponseDto processRequest(@RequestBody RequestDto requestDto);
}
Consumer Service - 数据传输对象(DTO)
package com.example.consumerservice.dto;
import lombok.Data;
/**
* 数据传输对象,用于发送请求信息
*/
@Data
public class RequestDto {
private String name;
private int age;
}
/**
* 响应数据传输对象,用于接收处理结果
*/
@Data
public class ResponseDto {
private String message;
private String name;
private int age;
}
Consumer Service - 用于测试的Controller
package com.example.consumerservice.controller;
import com.example.consumerservice.client.ProviderClient;
import com.example.consumerservice.dto.RequestDto;
import com.example.consumerservice.dto.ResponseDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 消费者服务的REST控制器
*/
@RestController
@RequestMapping("/api/consumer")
@Slf4j
public class ConsumerController {
private final ProviderClient providerClient;
@Autowired
public ConsumerController(ProviderClient providerClient) {
this.providerClient = providerClient;
}
/**
* 处理POST请求的方法
*
* @param requestDto 请求体中的数据
* @return 处理后的响应对象
*/
@PostMapping("/process")
public ResponseDto processRequest(@RequestBody RequestDto requestDto) {
ResponseDto responseDto = providerClient.processRequest(requestDto);
log.info("Received response from Provider Service: {}", responseDto.getMessage());
return responseDto;
}
}
测试
curl -X POST http://localhost:8082/api/consumer/process \
-H "Content-Type: application/json" \
-d '{"name": "John Doe", "age": 30}'
Respons:
{
"message": "Processed request for John Doe who is 30 years old.",
"name": "John Doe",
"age": 30
}
【值得收藏,将来说不定有用!】
上面只是说了@FeignClient注解,还有其他常用的注解,也一次性汇总给大家。
基本 CRUD操作
- 使用 @GetMapping, @PostMapping, @PutMapping, @DeleteMapping 来实现常见的 CRUD 操作。
带参数的请求
- 使用 @RequestParam 和 @PathVariable 来处理带有查询参数和路径变量的请求。
自定义请求头
- 使用 @RequestHeader 和 @Headers 来设置请求头,例如授权令牌或内容类型。
请求体处理
- 使用 @RequestBody 来处理 JSON 或 XML 格式的请求体。
复杂的查询参数
- 使用 @QueryMap 来处理包含多个查询参数的情况。
简单应用
package com.example.consumerservice.client;
import com.example.consumerservice.dto.RequestDto;
import com.example.consumerservice.dto.ResponseDto;
import feign.Headers;
import feign.Param;
import feign.QueryMap;
import feign.RequestLine;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@FeignClient(name = "providerService", url = "http://localhost:8081")
public interface ProviderClient {
// 使用 @PostMapping 处理 POST 请求
@PostMapping("/api/provider/process")
ResponseDto processRequest(@RequestBody RequestDto requestDto);
// 使用 @GetMapping 和 @RequestParam 处理带有查询参数的 GET 请求
@GetMapping("/api/provider/search")
List<Item> searchItems(@RequestParam("query") String query);
// 使用 @GetMapping 和 @PathVariable 处理带有路径变量的 GET 请求
@GetMapping("/api/provider/items/{id}")
Item getItemById(@PathVariable("id") Long id);
// 使用 @RequestHeader 设置请求头
@GetMapping("/api/provider/data")
Data getData(@RequestHeader("Authorization") String token);
// 使用 @Headers 设置静态请求头
@GetMapping(value = "/api/provider/data", headers = "Accept=application/json")
Data getDataWithStaticHeader();
// 使用 @QueryMap 处理复杂的查询参数
@GetMapping("/api/provider/search")
List<Item> searchItemsWithQueryMap(@QueryMap Map<String, Object> queryParams);
}
猜你喜欢
- 2025-05-08 都说Feign是RPC,没有侵入性,为什么我的代码越来越像 C++
- 2025-05-08 Spring 最常用的 7 大类注解,史上最强整理
- 2025-05-08 搞不清 Spring Boot 3 里 Feign 和 OpenFeign?一文吃透!
- 2025-05-08 缺氧是导致癌症转移的元凶!“加氧治疗”或是癌症治疗的关键
- 2025-05-08 Spring中如何使用多个@RequestMapping注解
- 2025-05-08 SpringBoot 常用注解总结超详细(springboot有什么注解)
- 2025-05-08 还在为 Spring Boot3 注解使用发愁?一文带你全掌握!
- 2025-05-08 简单说说什么是Feign?(简单说说什么是合适的飞机体型)
- 2024-07-22 写给新手看的 Spring Boot 入门学习指南
- 2024-07-22 全面解读 iOS 14 ATT和SKAdNetwork
- 05-08都说Feign是RPC,没有侵入性,为什么我的代码越来越像 C++
- 05-08Spring 最常用的 7 大类注解,史上最强整理
- 05-08搞不清 Spring Boot 3 里 Feign 和 OpenFeign?一文吃透!
- 05-08SpringBoot与OpenFeign整合,实现微服务之间的声明式API调用系统
- 05-08缺氧是导致癌症转移的元凶!“加氧治疗”或是癌症治疗的关键
- 05-08Spring中如何使用多个@RequestMapping注解
- 05-08SpringBoot 常用注解总结超详细(springboot有什么注解)
- 05-08还在为 Spring Boot3 注解使用发愁?一文带你全掌握!
- 最近发表
-
- 都说Feign是RPC,没有侵入性,为什么我的代码越来越像 C++
- Spring 最常用的 7 大类注解,史上最强整理
- 搞不清 Spring Boot 3 里 Feign 和 OpenFeign?一文吃透!
- SpringBoot与OpenFeign整合,实现微服务之间的声明式API调用系统
- 缺氧是导致癌症转移的元凶!“加氧治疗”或是癌症治疗的关键
- Spring中如何使用多个@RequestMapping注解
- SpringBoot 常用注解总结超详细(springboot有什么注解)
- 还在为 Spring Boot3 注解使用发愁?一文带你全掌握!
- 简单说说什么是Feign?(简单说说什么是合适的飞机体型)
- 浅谈3种css技巧——两端对齐(css文字两端对齐属性)
- 标签列表
-
- cmd/c (64)
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- js判断是否空对象 (63)
- pythoncase语句 (81)
- es6includes (73)
- sqlset (64)
- windowsscripthost (67)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)