OpenFeign
微服务之间的通信方式,通常有两种:RPC 和 HTTP。
简言之,RPC 就是像调用本地方法一样调用远程方法。
在 SpringCloud 中,默认是使用 HTTP 来进行微服务的通信,最常用的实现形式有两种:
- RestTemplate
- OpenFeigen
什么是 OpenFeign
OpenFeign 是一个声明式的 Web Service 客户端。它让微服务之间的调用变得更简单。
SpringCloud Feign 文档:Spring Cloud OpenFeign
OpenFeign 使用
引入依赖
给调用者添加如下依赖
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
添加注解
给调用者的启动类上添加 @EnableFeignClients

编写客户端
@FeignClient 用来指定访问的是哪个服务,value 值表示要访问的应用名称。

@FeignClient 中的 path = "/product" 表示在这个 ProductApi 接口的所有方法中,都会存在前缀 /product

更改调用代码:
java
import com.demo.order.api.ProductApi;
import com.demo.order.mapper.OrderMapper;
import com.demo.order.model.OrderInfo;
import com.demo.order.model.ProductInfo;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* @author hanzishuai
* @date 2025/03/07 19:19
* @Description
*/
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
@Autowired
private ProductApi productApi;
/**
* Created by hanzishuai on 2025/3/17
* Blog : https://blog.csdn.net/qrwitu142857 [原文地址,请尊重原创]
* @Description: 远程调用 getProductInfo 接口,获取商品信息
* @param orderId 商品 ID
* @return:
*/
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
// 更改之前的调用过程
// String url = "http://product-service/product/" + orderInfo.getProductId();
// ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
// 更改后的调用过程
ProductInfo productInfo = productApi.getProductInfo(orderInfo.getProductId());
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
参数传递
传递单个参数
使用 @RequestParam 来指定参数,@RequestParam 不可省略。
java
package com.demo.order.api;
import com.demo.order.model.ProductInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @author hanzishuai
* Blog : https://blog.csdn.net/qrwitu142857 [原文地址,请尊重原创]
* @date 2025/3/17 20:39
*/
// @FeignClient 用来指定访问的是哪个服务,value 表示要访问的应用名称。
// path = "/product" 表示在这个 ProductApi 接口的所有方法中,都会存在前缀 /product
@FeignClient(value = "product-service",path = "/product")
public interface ProductApi {
// @RequestMapping 用来指定要访问哪个 URL
@RequestMapping("/p1")
// 使用 @RequestParam 来指定参数,@RequestParam 不可省略。
String p1(@RequestParam("id") Integer id);
}
通过 Controller 调用远程方法:
java
package com.demo.order.controller;
import com.demo.order.api.ProductApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author hanzishuai
* Blog : https://blog.csdn.net/qrwitu142857 [原文地址,请尊重原创]
* @date 2025/3/17 21:37
*/
@RequestMapping("/feign")
@RestController
public class FeignController {
@Autowired
private ProductApi productApi;
/**
* Created by hanzishuai on 2025/3/17
* Blog : https://blog.csdn.net/qrwitu142857 [原文地址,请尊重原创]
* @Description: 通过远程调用,访问商品服务 p1 方法
*/
@RequestMapping("/o1")
public String o1(Integer id) {
return productApi.p1(id);
}
}
测试结果:

传递多个参数
java
package com.demo.order.api;
import com.demo.order.model.ProductInfo;
import org.springframework.cloud.openfeign.FeignClient;
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;
/**
* @author hanzishuai
* Blog : https://blog.csdn.net/qrwitu142857 [原文地址,请尊重原创]
* @date 2025/3/17 20:39
* @Description
*/
// @FeignClient 用来指定访问的是哪个服务,value 表示要访问的应用名称。
// path = "/product" 表示在这个 ProductApi 接口的所有方法中,都会存在前缀 /product
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
// 使用 @RequestParam 来指定参数,@RequestParam 不可省略。
@RequestMapping("/p2")
String p2(@RequestParam("id") Integer id, @RequestParam("name") String name);
}
通过 Controller 调用远程方法同上,就不写了。
传递对象
java
package com.demo.order.api;
import com.demo.order.model.ProductInfo;
import org.springframework.cloud.openfeign.FeignClient;
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;
/**
* @author hanzishuai
* Blog : https://blog.csdn.net/qrwitu142857 [原文地址,请尊重原创]
* @date 2025/3/17 20:39
* @Description
*/
// @FeignClient 用来指定访问的是哪个服务,value 表示要访问的应用名称。
// path = "/product" 表示在这个 ProductApi 接口的所有方法中,都会存在前缀 /product
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
// @SpringQueryMap 用来接受对象
@RequestMapping("/p3")
String p3(@SpringQueryMap ProductInfo productInfo);
}
通过 Controller 调用远程方法同上,就不写了。
传递 JSON
java
package com.demo.order.api;
import com.demo.order.model.ProductInfo;
import org.springframework.cloud.openfeign.FeignClient;
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;
/**
* @author hanzishuai
* Blog : https://blog.csdn.net/qrwitu142857 [原文地址,请尊重原创]
* @date 2025/3/17 20:39
* @Description
*/
// @FeignClient 用来指定访问的是哪个服务,value 表示要访问的应用名称。
// path = "/product" 表示在这个 ProductApi 接口的所有方法中,都会存在前缀 /product
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
@RequestMapping("/p4")
String p4(@RequestBody ProductInfo productInfo);
}
通过 Controller 调用远程方法同上,就不写了。
OpenFeign 最佳实践
继承
我们可以定义好一个接口,服务提供方实现这个接口,服务消费方编写 Feign 接口的时候,直接继承这个接口。
官方介绍:Spring Cloud OpenFeign Features :: Spring Cloud Openfeign
接口可以放在一个公共的 jar 包内,供服务提供方和服务消费方使用。
举个例子:假设 order-service 需要调用 product-service 。
- 我们可以新建一个 product-api ,把常用的操作封装到接口里。
java
package com.demo.product.api;
import com.demo.product.model.ProductInfo;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author hanzishuai
* Blog : https://blog.csdn.net/qrwitu142857 [原文地址,请尊重原创]
* @date 2025/3/17 22:20
* @Description
*/
public interface ProductInterface {
// @RequestMapping 用来指定要访问哪个 URL
@RequestMapping("/{productId}")
ProductInfo getProductById(@PathVariable("productId") Integer productId);
}
- 把当前 jar 包放到本地的 maven 仓库:

- 让 order-service、product-service 引入 刚刚打好的 jar 包。

- 继承 product-api 的接口:
java
package com.demo.product.controller;
import com.demo.product.api.ProductInterface;
import com.demo.product.model.ProductInfo;
import com.demo.product.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author hanzishuai
* @date 2025/03/11 18:31
* @Description
*/
@Slf4j
@RequestMapping("/product")
@RestController
public class ProductController implements ProductInterface {
@Autowired
private ProductService productService;
@RequestMapping("/{productId}")
public ProductInfo getProductById(@PathVariable Integer productId) {
log.info("接收参数,productId:" + productId);
return productService.selectProductById(productId);
}
}
抽取
将 Feign 的 Client 抽取为一个独立的模块,并把涉及到的实体类等都放在这个模块中,打成一个 jar。服务消费方只需要依赖该 jar 包即可。jar 包通常由服务提供方来实现。
举个例子:假设 order-service 需要调用 product-service 。
- 我们可以新建一个 product-api ,把常用的操作封装到接口里。
java
package com.demo.product.api;
import com.demo.product.model.ProductInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value = "product-service",path = "/product")
public interface ProductApi {
@RequestMapping("/{productId}")
ProductInfo getProductById(@PathVariable("productId") Integer productId);
}
- 把当前 jar 包放到本地的 maven 仓库:

- 让 order-service、product-service 引入 刚刚打好的 jar 包。

-
由于 Spring 只会扫描启动类下面的目录,而我们要使用的 api 不在启动类下面。所以需要使用@EnableFeignClients(clients = {ProductApi.class}) 来让 Spring 帮我们管理。
javapackage com.demo.order; import com.demo.product.api.ProductApi; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; /** * @author hanzishuai * @date 2025/03/07 17:30 * @Description */ @EnableFeignClients(clients = {ProductApi.class}) @SpringBootApplication public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }
-
服务调用方引入抽取出来的模块
java
package com.demo.product.controller;
import com.demo.product.model.ProductInfo;
import com.demo.product.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author hanzishuai
* @date 2025/03/11 18:31
* @Description
*/
@Slf4j
@RequestMapping("/product")
@RestController
public class ProductController{
@Autowired
private ProductService productService;
@RequestMapping("/{productId}")
public ProductInfo getProductById(@PathVariable Integer productId) {
log.info("接收参数,productId:" + productId);
return productService.selectProductById(productId);
}
}
服务部署
maven 打包默认是从远程仓库下载的,但是由于我们之前把 jar 包放到了本地的 maven 仓库,所以程序会启动失败。解决方案如下:
- 上传到 maven 仓库,如何发布Jar包到Maven中央仓库 - 简书。
- 搭建 maven 私服,上传 jar 包到私服。
- 从本地读取 jar 包。
在这里选择第三种方法。
- 把 jar 包放到本地的 maven 仓库

-
更改消费者服务的配置文件
xml<dependency> <groupId>org.example</groupId> <artifactId>product-api</artifactId> <version>1.0-SNAPSHOT</version> <scope>compile</scope> </dependency>
把
compile
改为system
,并添加systemPath
xml<dependency> <groupId>org.example</groupId> <artifactId>product-api</artifactId> <version>1.0-SNAPSHOT</version> <scope>system</scope> <systemPath>C:/Users/awa/.m2/repository/org/example/product-api/1.0-SNAPSHOT/product-api-1.0-SNAPSHOT.jar</systemPath> </dependency>
systemPath
的位置在:更改
spring-boot-maven-plugin
添加configuration
并将includeSystemScope
设置为 truexml<plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <includeSystemScope>true</includeSystemScope> </configuration> </plugin> </plugins>
刷新 maven
剩下的参考 博客系统笔记总结 2( Linux 相关) 中的部署 Web 项目到 Linux。
本文到这里就结束啦~
