跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Spring Cloud OpenFeign 远程调用最佳实践

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

孤勇者发布于 2026/3/26更新于 2026/7/1441 浏览
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. 远程访问
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 飞算 Java AI 安装与项目生成实战指南
  • AI 终端生态重构与视觉感知驱动的实体交互实践
  • Claude Skills 与 MCP 对比:为何 Skills 更省 Token 且易用
  • 淘宝超市卡 TopAPI 接入实战:Spring Boot + Lombok 实现方案
  • HDFS 分布式文件系统编程实践
  • Python 爬虫实战:金融机构贷款产品信息抓取
  • 宇树机器人 G1 二次开发:导航仿真与地图转换教程
  • AutoFigure:从长文本到出版级科研插图的 AI 生成框架
  • Spring Cloud OpenFeign 远程调用最佳实践
  • AI 时代,普通人如何脱颖而出?
  • 零公网 IP 安全访问本地 AI 服务的方法与实践
  • Python 实现十大经典排序算法详解:原理、代码与复杂度分析
  • Telegram 机器人 Token 与 ChatID 获取实战指南
  • 相干伊辛机在医疗与医疗 AI 领域的应用前景
  • 基于 Java 和 Leaflet 的湖南省道路长度 WebGIS 系统构建
  • 理解MySQL事务隔离:读未提交、读提交、可重复读和串行化
  • Windows 11 部署 Langchain-Chatchat 本地知识库指南
  • 手势控制电脑方案分析与 Python 实战
  • 无人机视觉任务常用数据集汇总:检测与分割资源整理
  • AIGC 产品经理面试高频 100 题答案解析

相关免费在线工具

  • 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