Appearance
Feign Client
1. 快速开始
引入依赖
依赖管理:
XML<properties> <spring-boot.version>2.7.6</spring-boot.version> <spring-cloud.version>2021.0.8</spring-cloud.version> </properties> <dependencyManagement> <dependencies> <!-- Spring Boot Dependencies --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Spring Cloud Dependencies --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>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
26Open Feign 依赖:
XML<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>1
2
3
4添加注解开启 Feign Client
Java@EnableFeignClients(basePackages = "your.feign.client.package.path") @SpringBootApplication public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } }1
2
3
4
5
6
7编写 Feign Client
Java@FeignClient("user-service") public interface RemoteUserService { @GetMapping("/user/{id}") UserVO getUser(@PathVariable("id") int id); }1
2
3
4
5使用 Feign Client 替代 RestTemplate
原代码:
JavarestTemplate.getForObject("http://user-service/user/" + id, UserVO.class)新代码:
JavaremoteUserService.getUser(id)
2. 自定义配置
2.1. 日志
Spring OpenFeign 支持以下几种日志模式:
NONE:无日志记录(默认);BASIC:仅记录请求方法、URL、响应状态码和执行时间;HEADERS:记录基本信息以及请求和响应的头部信息;FULL:记录请求和响应的头部、正文和元数据。
配置方式:
通过配置文件配置:
YAMLfeign: client: config: default: # 全局配置 logger-level: full user-service: # 针对 user-service 的配置 logger-level: full通过 Java 代码配置:
Spring Cloud 会使用
FeignClientsConfiguration为每个命名客户端按需创建一个新的ApplicationContext。Javapublic class MyFeignClientConfiguration { @Bean public Logger.Level feignLoggerLevel() { return Logger.Level.BASIC; } }1
2
3
4
5
6Java@FeignClient(value = "user-service", configuration = MyFeignClientConfiguration.class) public interface RemoteUserService { @GetMapping("/user/{id}") UserVO getUser(@PathVariable("id") int id); }1
2
3
4
5在这种情况下,
user-service客户端由FeignClientsConfiguration中的组件以及MyFeignClientConfiguration中的组件共同组成(后者会覆盖前者)。警告
MyFeignClientConfiguration不需要使用@Configuration注解。然而,如果加了这个注解,则必须确保它不会被@ComponentScan扫描到。否则,当你在@FeignClient中指定该配置类时,它会成为默认的feign.Decoder、feign.Encoder、feign.Contract等组件的来源。避免这种情况的方法包括:- 将它放在一个与
@ComponentScan或@SpringBootApplication不重叠的包中; - 或者在
@ComponentScan中明确将其排除。
也可以将
MyFeignClientConfiguration作为全局配置:Java@EnableFeignClients( basePackages = "your.feign.client.package.path", defaultConfiguration = MyFeignClientConfiguration.class) @SpringBootApplication public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } }1
2
3
4
5
6
7
8
9- 将它放在一个与
2.2. 重试机制
OpenFeign 的默认 Retryer 实现是 Retryer.NEVER_RETRY。这意味着默认情况下,OpenFeign 本身不会进行任何重试!当 OpenFeign 与 Ribbon 或 SCLB(Spring Cloud LoadBalancer)集成时,负载均衡器负责从服务注册中心(如 Eureka, Nacos)获取可用实例列表,并根据策略选择一个实例进行调用。
Feign Retryer: 发生在 Feign 客户端层面,决定对同一个目标 URL(这个 URL 在负载均衡后已经指向一个具体的实例)是否重试;
LB 重试:发生在负载均衡器层面,决定当一个服务实例调用失败后,是重试同一个实例还是切换到下一个实例再试(或者两者结合)。
提示
LB 重试与 Feign 重试需协调,避免嵌套(例如,Feign 重试 3 次 × LoadBalancer 切换 2 次 = 6 次总调用)。
常用的负载均衡器有 Ribbon 和 Spring Cloud LoadBalancer。鉴于 Spring Cloud 2020 以后,Ribbon 已被弃用,推荐使用 Spring Cloud LoadBalancer。
SCLB 集成的 OpenFeign 默认不提供开箱即用的重试机制(无论是 LB 切换还是同一实例重试)。需要显式配置。可以通过引入 spring-retry 来支持重试。
2.3. HTTP 客户端
Feign 底层客户端默认实现是 URLConnection,不支持连接池,建议替换为 HttpClient 或 OKHttp。
当与 SCLB 集成时,Spring 会使用 LoadBalancerFeignClient 代理真正的 HTTP 请求客户端,以实现客户端负载均衡。
以 HttpClient 为例:
引入依赖:
XML<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>配置连接池:
YAMLfeign: httpclient: enabled: true max-connections: 200 max-connections-per-route: 50
3. 最佳实践
方式一,给消费者的 Feign Client 和提供者的 Controller 定义统一的父接口;
Javapublic interface UserAPI { @GetMapping("/user/{id}") UserVO getUser(@PathVariable("id") int id); }Java@FeignClient("user-service") public interface RemoteUserService extends UserAPI {}Java@RestController public class UserAPI implements UserAPI { public UserVO getUser(@PathVariable("id") int id) { // ... } }该方案虽然可以避免在 Feign Client 中编写重复、冗余的代码,但是缺点也很明显:
- Feign Client 与 Controller 紧耦合;
- 父接口参数列表中的注解不会被 Controller 继承,需要重复再声明一次。
方式二,将 Feign Client 抽取为独立模块,并把默认的 Feign 配置都放到这个模块中;
操作步骤:
创建一个 Module,引入 Feign 的 Starter 依赖;
将 Feign Client 相关接口定义以及默认的 Feign 配置类都挪到该 Module 中;
其它业务模块引入该 Module,保留原启动类上的
@EnableFeignClients并根据需要调整basePackages配置值;调整相关
import,启动测试。