跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
Javajava

Spring Cloud OpenFeign 远程调用最佳实践

综述由AI生成阐述了 Spring Cloud OpenFeign 在微服务架构中的两种最佳实践模式:继承方式与抽取方式。通过构建独立 Module 封装接口与实体类,实现服务提供方与消费方的解耦。内容详细讲解了依赖引入、接口定义、Maven 打包、服务部署及 Nacos 注册中心配置流程,解决了 Feign 客户端与 Controller 代码重复的问题,提升了企业级开发的规范性与可维护性。

孤勇者发布于 2026/3/26更新于 2026/5/1019 浏览
Spring Cloud OpenFeign 远程调用最佳实践

OpenFeign 最佳实践

问题引入

最佳实践是在项目实践中总结出的使用方式。

通过观察,我们也能看出来,Feign 的客户端与服务提供者的 Controller 代码非常相似:

Feign 客户端

@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
    @RequestMapping("/{productId}")
    ProductInfo getProductById(@PathVariable("productId") Integer productId);
}

服务提供方 Controller

@RequestMapping("/product")
@RestController
public class ProductController {
    @Autowired
    private ProductService productService;

    @RequestMapping("/{productId}")
    public ProductInfo getProductById(@PathVariable("productId") Integer productId) {
        return productService.selectProductById(productId);
    }
}

那么有没有一种方法可以简化这种写法呢?

Feign 继承方式

Feign 支持继承的方式,我们可以把一些常见的操作封装到接口里。我们可以定义好一个接口,服务提供方实现这个接口,服务消费方编写 Feign 接口的时候,直接继承这个接口。

创建 Module

接口可以放在一个公共的 Jar 包里,供服务提供方和服务消费方使用。

引入依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>
编写接口

把之前 ProductApi 的内容移动到 Module 中的 ProductInterface 接口中:

package api;
import model.ProductInfo;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

public interface ProductInterface {
    @RequestMapping("/{productId}")
    ProductInfo getProductById(@PathVariable("productId") Integer productId);

    @RequestMapping("/p1")
    String p1(@RequestParam("id") Integer id);

    @RequestMapping("/p2")
    String p2(@RequestParam("id") Integer id, @RequestParam("name") String name);

    @RequestMapping("/p3")
    String p3(@SpringQueryMap ProductInfo productInfo);

    @RequestMapping("/p4")
    String p4(@RequestBody ProductInfo productInfo);
}

把之前 ProductInfo 的内容移动到 Module 中:

package model;
import lombok.Data;
import java.util.Date;

@Data
public class ProductInfo {
    private Integer id;
    private String productName;
    private Integer productPrice;
    private Integer state;
    private Date createTime;
    private Date updateTime;
}

目录结构如下:

打 Jar 包

通过 Maven 打包。

观察 Maven 本地仓库,Jar 包是否打成功。

服务提供方

服务提供方实现接口 ProductInterface。

package product.controller;
import api.ProductInterface;
import model.ProductInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import product.service.ProductService;

@RequestMapping("/product")
@RestController
public class ProductController implements ProductInterface {
    @Autowired
    private ProductService productService;

    @RequestMapping("/{productId}")
    public ProductInfo getProductById(@PathVariable("productId") Integer productId) {
        return productService.selectProductById(productId);
    }

    @RequestMapping("/p1")
    public String p1(Integer id) {
        return "product-service 接收到参数,id:" + id;
    }

    @RequestMapping("/p2")
    public String p2(Integer id, String name) {
        return "product-service 接收到参数,id:" + id + ",name:" + name;
    }

    @RequestMapping("/p3")
    public String p3(ProductInfo productInfo) {
        return "product-service 接收到参数:" + productInfo.toString();
    }

    @RequestMapping("/p4")
    public String p4(@RequestBody ProductInfo productInfo) {
        return "product-service 接收到参数:" + productInfo.toString();
    }
}
服务消费方

服务消费方继承 ProductInterface。

package order.api;
import api.ProductInterface;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient(value = "product-service", path = "/product")
public interface ProductApi extends ProductInterface {
}
启动服务并访问

Feign 抽取方式

官方推荐 Feign 的使用方式为继承的方式,但是企业开发中,更多是把 Feign 接口抽取为一个独立的模块(做法和继承相似,但理念不同)。

操作方法:将 Feign 的 Client 抽取为一个独立的模块,并把涉及到的实体类等都放在这个模块中,打成一个 Jar。服务消费方只需要依赖该 Jar 包即可。这种方式在企业中比较常见,Jar 包通常由服务提供方来实现。

创建 Module
引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
编写接口

把之前 ProductApi 的内容移动到 Module 中的 ProductInterface 接口中:

package api;
import model.ProductInfo;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

public interface ProductInterface {
    @RequestMapping("/{productId}")
    ProductInfo getProductById(@PathVariable("productId") Integer productId);

    @RequestMapping("/p1")
    String p1(@RequestParam("id") Integer id);

    @RequestMapping("/p2")
    String p2(@RequestParam("id") Integer id, @RequestParam("name") String name);

    @RequestMapping("/p3")
    String p3(@SpringQueryMap ProductInfo productInfo);

    @RequestMapping("/p4")
    String p4(@RequestBody ProductInfo productInfo);
}

把之前 ProductInfo 的内容移动到 Module 中:

package model;
import lombok.Data;
import java.util.Date;

@Data
public class ProductInfo {
    private Integer id;
    private String productName;
    private Integer productPrice;
    private Integer state;
    private Date createTime;
    private Date updateTime;
}

目录结构如下:

打 Jar 包

通过 Maven 打包。

观察 Maven 本地仓库,Jar 包是否打成功。

服务消费方

删除 ProductInfo 和 ProductApi,引入依赖。

<dependency>
    <groupId>com.wmh</groupId>
    <artifactId>product-api</artifactId>
    <version>1.0-SNAPSHOT</version>
    <scope>compile</scope>
</dependency>

指定扫描类:下面我们使用 @EnableFeignClients(clients = {ProductApi.class}) 来指定扫描类,当然也可以使用 @EnableFeignClients(basePackages = {"api"}) 指定扫描类。

package order;
import api.ProductApi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients(clients = {ProductApi.class})
@SpringBootApplication
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

如果不指定扫描类的话,运行程序会失败并报错。原因是因为 order-service 的启动类 OrderServiceApplication 只会扫描启动类所在目录,而 ProductApi 并不在其扫描路径内,因此需要指定扫描类。

启动服务并访问

服务部署

  1. 修改数据库、Nacos 等相关配置。
  2. 对两个服务进行打包。 Maven 打包默认是从远程仓库下载的,product-api 这个包在本地,有以下解决方案:
  • 上传到 Maven 中央仓库(比较麻烦)[不推荐]
  • 搭建 Maven 私服,上传 Jar 包到私服 [企业推荐]
  • 从本地读取 Jar 包 [个人学习阶段推荐]

前两种方法比较复杂,咱们使用第三种方式。

修改 pom.xml 文件

如果不配置以下内容,项目启动会失败并报错:

<configuration>
    <includeSystemScope>true</includeSystemScope>
</configuration>
  1. 上传 jar 到 Linux 服务器。
  2. 启动 Nacos。启动前最好把 data 数据删除掉。
  3. 启动服务。
# 后台启动 order-service,并设置输出日志到 logs/order.log
nohup java -jar order-service.jar >logs/order.log &

# 后台启动 product-service,并设置输出日志到 logs/product-9090.log
nohup java -jar product-service.jar >logs/product-9090.log &

# 启动实例,指定端口号为 9091
nohup java -jar product-service.jar --server.port=9091 >logs/product-9091.log &
观察 Nacos 控制台
远程访问

目录

  1. OpenFeign 最佳实践
  2. 问题引入
  3. Feign 继承方式
  4. 创建 Module
  5. 引入依赖
  6. 编写接口
  7. 打 Jar 包
  8. 服务提供方
  9. 服务消费方
  10. 启动服务并访问
  11. Feign 抽取方式
  12. 创建 Module
  13. 引入依赖
  14. 编写接口
  15. 打 Jar 包
  16. 服务消费方
  17. 启动服务并访问
  18. 服务部署
  19. 修改 pom.xml 文件
  20. 后台启动 order-service,并设置输出日志到 logs/order.log
  21. 后台启动 product-service,并设置输出日志到 logs/product-9090.log
  22. 启动实例,指定端口号为 9091
  23. 观察 Nacos 控制台
  24. 远程访问
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 常见排序算法详解:插入、希尔、选择、冒泡、堆、快速、归并及计数
  • Open WebUI 部署指南:为本地 Ollama 模型搭建图形化交互界面
  • Mac 轻量安装 Docker 完整指南(Docker + Colima + K8s)
  • 交通管理在线服务系统设计:SpringBoot2+Vue3+MyBatis-Plus 实战
  • AIGC 工具助力 2D 游戏美术全流程实战指南
  • Llama Factory 快速切换 Alpaca 与 Vicuna 对话模板
  • 鸿蒙车载互联实战:用分布式技术重构出行体验
  • Open-Lovable 网页克隆工具及 cpolar 远程访问配置
  • C++ 类与对象进阶:初始化列表、静态成员与编译器优化
  • 国内主流大模型盘点:Kimi、通义千问等八大模型对比分析
  • Python 函数、列表与元组核心用法详解
  • 国产麒麟操作系统 V10 X86 架构离线安装 Nginx 及配置管理
  • Whisper-Large-V3-Turbo:语音识别技术架构与性能分析
  • Magnet Player:基于 Web 的磁力链媒体播放器
  • 基于 nanobot 搭建轻量级 QQ AI 机器人及搜索功能优化
  • ES6 语法详解:进制、Symbol 与 Class
  • AI 工具泛滥时代,为何核心能力反而更稀缺?
  • MoltBot 机器人集成钉钉 Stream 流式接入配置指南
  • AI 编程工具选型:Copilot、Cursor、Codex 核心差异
  • 在线 OJ 系统竞赛管理模块实战 (Java/Spring/Vue)

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online