Feign 实战指南:从 REST 替代到性能优化与最佳实践

发布于:2025-06-28 ⋅ 阅读:(11) ⋅ 点赞:(0)

在微服务架构中,服务间通信的便捷性与性能至关重要。本文将深入解析 Feign 这一声明式 HTTP 客户端的核心应用,从替代传统 RestTemplate 解决调用痛点开始,逐步讲解自定义配置、性能优化及企业级最佳实践。内容涵盖 Feign 客户端定义与使用、日志级别配置、连接池优化,以及通过继承或抽取方式实现代码复用,帮助读者构建高效、可维护的微服务通信体系。
在这里插入图片描述

本专栏的内容均来自于 B 站 UP 主黑马程序员的教学视频,感谢你们提供了优质的学习资料,让编程不再难懂。
专栏地址 : https://blog.csdn.net/m0_53117341/category_12835102.html

一 . Feign 替代 RestTemplate

1.1 RestTemplate 方式调用存在的问题

先来看一下我们之前通过 RestTemplate 方式发起远程调用的代码

这种方式是通过 URL 地址指明要访问的服务名称、请求路径以及请求的参数信息 , 由 RestTemplate 来负责发起请求 .

那这段代码存在一些问题 :

  1. 代码可读性差 , 编程体验不够统一 : 在代码中引入了 URL , 导致代码不够统一
  2. 参数复杂 URL 难以维护 : 就比如我们访问的 Nacos 的路径 , 他的 URL 就非常复杂

1.2 Feign 的介绍

Feign 是一个声明式的 HTTP 客户端

声明式 : 比如早期 , 我们学习 MySQL 的事务的时候 , 我们需要手动开启关闭事务 . 那后来学习 Spring 的时候 , 就提供了 Spring 的声明式事务 , 只需要在配置文件中告诉 Spring 对谁加事务即可 . 那以后所有的事务都交给 Spring 来帮我们做 .

那声明式 HTTP 客户端也是如此 , 我们如果要发送 HTTP 请求 , 那只需要将发送请求所需要的信息声明出来 , 剩下的工作 Feign 帮助我们来做 .

Feign 的作用就是帮助我们优雅地实现 HTTP 请求的发送 , 解决上面提出的问题 .

1.3 定义和使用 Feign 客户端

1.3.1 引入依赖

在 order-service 服务中引入依赖

<!-- Feign 客户端依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

1.3.2 添加注解

我们需要在 order-service 的启动类中添加开启 Feign 功能的注解

package com.example.order;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@MapperScan("com.example.order.mapper")
@SpringBootApplication
@EnableFeignClients // 开启 Feign 功能
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    // 随机轮训
    // @Bean
    // public IRule randomRule() {
    //     return new RandomRule();
    // }
}

1.3.3 编写 Feign 的客户端进行接口声明

我们需要在该接口上添加一个注解 @FeignClient , 并且指定要调用的服务名称

接下来我们编写一个方法 , 方法的返回值就是我们想获取到的结果 , 方法的参数就是我们要传的参数

然后我们还需要在方法上面添加一个注解声明请求的方式

那上面用了动态参数 , 下面我们也需要用 @PathVariable 注解来接收

package com.example.order.clients;

import com.example.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

// 1. 指定服务名称
@FeignClient("userservice")
public interface UserClient {

    // 3. 声明请求方式
    @GetMapping("/user/{id}")
    // 4. 在参数前面加上 @PathVariable 注解
    User findById(@PathVariable("id") Long id);// 2. 方法的返回值就是我们想获取到的结果 , 方法的参数就是我们要传的参数
}

1.3.4 测试

那我们可以将之前的 RestTemplate 的方式替换掉了

首先 , 需要注入刚才实现的 UserClient

然后下面我们只需要调用 userClient 的 findById 方法即可

package com.example.order.service;

import com.example.order.clients.UserClient;
import com.example.order.mapper.OrderMapper;
import com.example.order.pojo.Order;
import com.example.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private UserClient userClient;

    public Order queryOrderById(Long orderId) {
        // 1. 查询订单
        Order order = orderMapper.findById(orderId);

        // 2. 查询用户 ID
        Long userId = order.getUserId();

        // 3. 用 Feign 来实现远程调用
        User user = userClient.findById(userId);

        // 4. 将获取到的用户信息添加到 user 字段中
        order.setUser(user);

        // 5. 返回
        return order;
    }
}

在运行之前 , 先将我们的 bootstrap.yml 修改回 8848

成功通过 Feign 的方式调用

小结

Feign 的使用步骤

  1. 引入依赖
  2. 在启动类中添加 @EnableFeignClients 注解
  3. 编写 FeignClient 接口
  4. 使用 FeignClient 中定义的方法代替 RestTemplate

1.4 通过 POST 请求进行远程调用

第一步 : 添加 @PostMapping 注解

第二步 : 参数使用 @RequestBody 接收即可

package com.example.order.clients;

import com.example.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

// 1. 指定服务名称
@FeignClient("userservice")
public interface UserClient {

    // 3. 声明请求方式
    @PostMapping("/user/add")
    // 4. 在参数前面加上 @RequestBody 注解
    String add(@RequestBody User user);
}

二 . 自定义配置

Feign 可以运行自定义的配置来去覆盖默认的配置 , 那可以修改的配置如下 :

类型 作用 说明
feign.Logger.Level 修改日志的级别 包含四种不同的级别 :
+ NONE
+ BASIC
+ HEADERS
+ FULL
feign.codec.Decoder 解码 将 HTTP 远程调用的结果进行解析 (比如 : 解析 JSON 字符串为 Java 对象)
feign.codec.Encoder 编码 将请求参数进行编码 (比如转化成二进制) , 便于通过 HTTP 请求发送
feign.Contract 支持的注解格式 默认是 Spring MVC 的注解
feign.Retryer 失败重试机制 请求失败的重试机制 , 一般通过 Ribbon 的重试机制

日志的级别分为四种 :

  • NONE : 不记录任何日志信息 , 这是默认值
  • BASIC : 仅记录请求的方法、URL 以及相应状态码和执行时间
  • HEADERS : 在 BASIC 的基础上 , 额外记录了请求和响应的头信息
  • FULL : 记录所有请求和响应的明细 , 包括 : 头信息、请求体、元数据

在非生产环境中 , 我们一般配置日志级别为 FULL , 在生产环境中 , 我们一般配置日志级别为 NONE / BASIC

那配置 Feign 日志有两种方式

2.1 配置文件方式

我们可以配置日志级别全局生效 , 也可以设置日志级别局部生效

① 全局生效

feign:
  client:
    config:
      default: # 默认全局所有的 Feign 的客户端都设置为该级别
        loggerLevel: FULL # 设置日志级别

那我们可以重启 order-service 服务 , 然后观察它的日志

日志中展示了详细的 HTTP 信息 .

② 局部生效

feign:
  client:
    config:
      default: # 针对所有微服务来去做配置
        loggerLevel: FULL # 设置日志级别
      userservice: # 针对特定的微服务来去做配置
        loggerLevel: BASIC

那 default 以及 userservice 配置全部存在 , 最后会执行哪个配置呢 ?

重启 user-service 服务

那这就代表最后会执行 userservice 这一层配置

2.2 Java 代码方式

我们可以声明一个配置类

然后在类上添加 @Configuration 注解 , 表示当前的类是一个配置类

然后在里面声明一个 Bean 对象 , 我们也能够看到一系列选项

package com.example.order.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig {
    @Bean
    public Logger.Level loggerLevel() {
        return Logger.Level.BASIC;
    }
}

① 全局配置

如果是全局配置 , 那就将他放到启动类中的 @EnableFeignClients 注解中

我们可以看一下 @EnableFeignClients 注解的选项

那我们就使用这个默认的参数

那就代表 , 所有的 Feign 都会走 FeignConfig 这个配置

② 局部配置

如果是局部配置 , 那就将他放到 @FeignClient 这个注解中

我们也可以看一下 @FeignClient 注解默认的选项

那我们就可以在选项中填写这个参数

package com.example.order.clients;

import com.example.order.config.FeignConfig;
import com.example.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

// 1. 指定服务名称
@FeignClient(value = "userservice", configuration = {FeignConfig.class})
public interface UserClient {

    // 3. 声明请求方式
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);// 2. 方法的返回值就是我们想获取到的结果, 方法的参数就是我们要传的参数
}

那我们就需要将配置文件中配置的自定义配置注释掉

重启一下

2.3 小结

Feign 的日志配置 :

方式一是配置文件 , feign.clent.config.xxx.loggerLevel

  1. 如果 xxx 是 default , 则代表全局
  2. 如果 xxx 是服务名称 , 例如 : userservice 就代表某服务

方式二是通过 Java 代码配置 Logger.Level 这个 Bean

  1. 如果在 @EnableFeignClients 注解中声明 , 则代表全局
  2. 如果在 @FeignClient 注解中声明 , 则代表某服务

三 . 性能优化

Feign 底层的客户端实现 :

  • URLConnection : 默认实现 , 不支持连接池
  • Apache HttpClient : 支持连接池
  • OKHttp : 支持连接池

因此 , 提高 Feign 的性能的主要手段就是使用连接池代替默认的 URLConnection

那这里我们用 Apache 的 HttpClient 来演示

3.1 引入依赖

在 order-service 的 pom.xml 中引入依赖 :

<!-- httpClient 的依赖 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

3.2 配置连接池

在 order-service 的 applicatiom.yml 中添加配置

feign: 
  httpclient:
    enabled: true # 开启 feign 对 HttpClient 的支持
    max-connections: 200 # 设置最大连接数
    max-connections-per-route: 50 # 设置每个路径的最大连接数

设置每个路径的最大连接数的意思就是设置每个接口的最大连接数

我们重新运行一下 , 由于这个性能提升了多少我们不太好衡量 , 所以保证不报错就可以了

3.3 小结

Feign 的优化 :

  1. 使用 HttpClient 或者 OKHttp 代替 URLConnection
    1. 引入 feign-httpClient 依赖
    2. 配置文件开启 httpClient 功能 , 设置连接池参数
  2. 日志级别生产环境尽量用 basic

四 . 最佳实践

所谓的最佳实践 , 就是在使用的过程中总结出来的经验 , 找到的最好的一种使用方式 .

我们提供给大家两种最佳的实践思路

4.1 继承方式

继承方式就是给消费者的 FeignClient 和提供者的 controller 定义统一的父接口作为标准

我们可以通过代码来了解一下

首先 , 来看 UserClient 这个接口 , 它的作用就是通过注解来声明远程调用需要用到的一系列的信息 , 比如 : 请求的方式、路径、参数、返回值类型等等 .

那这段代码的意义就是让消费者基于这些信息发送一个 HTTP 请求 , 那这个请求最终就会到达 user-service 服务中的某个实例上 .

那我们再来看一下 user-service 服务中的 controller 层的代码

那我们可以对比一下两个方法

那这两个方法非常非常相似 , 那我们可不可以对这两个方法进行一个抽取呢 ?

但是这种方式其实是存在一些问题的 , Spring 官方也提醒我们了

一般情况下 , 我们不推荐接口被服务端和客户端所共享 . 因为他会造成紧耦合 , 将来接口发生变化 , 服务端和客户端就都需要进行更改 . 而且这种方案是不对 Spring MVC 起作用的 .

4.2 抽取方式

我们可以将 FeignClient 抽取为独立模块 , 并且把接口有关的 POJO、默认的 Feign 配置都放到这个模块中 , 提供给所有消费者使用 .


Feign 的最佳实践 :

  1. [继承方式] : 让 controller 和 FeignClient 继承同一接口
  2. [抽取方式] : 将 FeignClient、POJO、Feign 的默认配置都定义到一个项目中 , 供所有消费者使用

4.3 实现抽取方式

① 抽取

首先 , 创建一个 module , 命名为 feign-api , 然后引入 feign 的 starter 依赖

然后在 feign-api 中引入 feign 的依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

然后我们就需要将 order-service 中的 Feign 客户端、配置、实体类都挪动到 feign-api 模块下

然后我们将这几个类该导的包导入进来

那之后其他服务想要进行远程调用 , 只需要调用 feign-api 即可 , 所以我们的 order-service 服务中的这些内容就可以删除掉了

那此时 Order 实体类会报错

② 在 order-service 中使用 feign-api

我们只需要引入 feign-api 依赖即可

<!-- 引入 feign-api -->
<dependency>
    <groupId>com.example.demo</groupId>
    <artifactId>feign-api</artifactId>
    <version>1.0</version>
</dependency>

此时我们重新导入一下包即可 , 我们需要重新导入 Order 实体类、OrderService、启动类这三个部分

③ 重启测试

那我们重启 order-service 服务

他报错的信息是未发现 UserClient 对象 , 这是因为我们已经将 UserClient 移动到 feign-api 模块下了 , 现在他们已经属于不同的服务了 , 我们不能跨服务调用 .

④ 解决扫描包问题

当定义的 FeignClient 不在 SpringBootApplication 的扫描包范围时 , 这些 FeignClient 无法使用 . 那我们有两种解决方案

  1. 指定 FeignClient 所在的包
@EnableFeignClients(basePackages = "com.example.feign.UserClient")
  1. 指定 FeignClient 字节码
@EnableFeignClients(clients = {UserClient.class})

那我们可以实现一下第二种方式

package com.example.order;

import com.example.feign.clients.UserClient;
import com.example.feign.config.FeignConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@MapperScan("com.example.order.mapper")
@SpringBootApplication
@EnableFeignClients(clients = {UserClient.class}, defaultConfiguration = {FeignConfig.class})
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

那我们再重启一下


小结 :

不同包的 FeignClient 的导入有两种方式 :

  1. 在 @EnableFeignClients 注解中添加 basePackages , 指定 FeignClient 所在的包
  2. 在 @EnableFeignClients 注解中添加 clients , 指定具体 FeignClient 的字节码

五 . 扩展

5.1 Feign 调用注意事项

我们先来回顾一下目前所做的工作

order-service 远程调用 user-service 这个微服务 , 我们无需再使用 RestTemplate 这种方式了 , 我们使用 Feign 这种声明式的客户端 .

我们目前已经引入了 Feign 客户端以及 HTTP 连接池的依赖

然后在 order-service 服务的启动类中开启了 Feign 远程调用

然后我们定义了一个接口 , 在后面指定了我们要调用的微服务 , 那每个微服务也需要暴露自己的接口 .

那目前我们是传一个参数的 , 如果传两个参数呢 ?

那我们要想访问这个接口 , URL 就需要长成这个样子 : http://localhost:8081/user/testData2?userName=xx&age=18

那我们如何定义这个接口对应的 Feign 方法呢 ?

直接将这个方法体部分复制过去

此时我们重启 order-service 与 user-service 服务 , 发现 order-service 就已经报错了

那我们怎样解决这个问题呢 ?

我们需要在 Feign 部分的参数前面添加 @RequestParam(“”) 参数表示从 query string 中获取数据

此时重新运行就不报错了


小结 :

  1. Feign 核心能力与使用
    1. 替代 RestTemplate:通过声明式接口简化远程调用,解决 URL 维护复杂、代码可读性差的问题,使用步骤包括引入依赖、添加注解、定义客户端接口。
    2. 请求方式支持:支持 GET、POST 等多种请求方式,POST 请求需通过 @RequestBody 传递参数。
  2. 自定义配置与日志
    1. 配置类型:可自定义日志级别、编解码器、重试机制等,日志级别分为 NONE、BASIC、HEADERS、FULL 四级。
    2. 配置方式:支持配置文件(全局 / 局部)和 Java 代码(全局 / 局部)两种方式,优先级为局部配置 > 全局配置。
  3. 性能优化实践
    1. 连接池优化:使用 Apache HttpClient 或 OKHttp 替代默认 URLConnection,通过配置连接池参数(最大连接数、路径连接数)提升性能。
    2. 日志策略:生产环境建议使用 BASIC 级别减少日志开销。
  4. 最佳实践与扩展
    1. 代码复用:推荐抽取 FeignClient 为独立模块,共享 POJO 和配置,避免继承方式的紧耦合问题。
    2. 多参数调用:使用 @RequestParam 明确标注查询参数,确保远程调用参数解析正确。

网站公告

今日签到

点亮在社区的每一天
去签到