
🔥我的主页: 九转苍翎
⭐️个人专栏: 《Java SE》 《Java集合框架系统精讲》 《MySQL高手之路:从基础到高阶》 《计算机网络》 《Java工程师核心能力体系构建》 《RabbitMQ理论与实践》
天行健,君子以自强不息。
0.前言
- SpringBoot版本:
3.2.5 - SpringCloud版本:
2023.0.3 - SpringCloud Alibaba版本:
2023.0.1.0 - nacos版本:
2.2.3(已免费上传至我的资源) - 项目源码 :spring-cloud-blog
在引入 Nacos 等服务发现组件后,服务调用方虽然不再需要记忆具体的 IP 地址,只需知道服务提供方的服务名即可发起调用,但其底层本质仍然是基于 IP 和端口的 URL 拼接与 HTTP 请求。
java
@Service
public class OrderService {
private final OrderMapper orderMapper;
private final RestTemplate restTemplate;
@Autowired
public OrderService(OrderMapper orderMapper, RestTemplate restTemplate) {
this.orderMapper = orderMapper;
this.restTemplate = restTemplate;
}
public OrderInfo selectOrderById(Integer id) {
OrderInfo orderInfo = orderMapper.selectOrderById(id);
String url = "http://product-service/product/getProductById?id=" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
为了进一步简化这一过程,Spring 官方在 Spring Framework 5 中已将传统的 RestTemplate 标记为 @Deprecated(已弃用)。与此同时,Spring 团队对 Netflix 开源的 OpenFeign 组件进行了封装,推出了 Spring Cloud Feign,通过声明式的编程模型,更优雅地实现了微服务间的远程调用:像调用本地方法一样调用远程服务
1.快速上手
1.1 引入依赖
在order-service服务的pom.xml文件中添加如下依赖
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.2 启动Feign客户端的功能
在order-service服务的启动类上添加@EnableFeignClients
java
@EnableFeignClients // 激活并启动 Feign 客户端的功能。添加此注解后,Spring 容器才会自动扫描 @FeignClient 注解的接口
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
1.3 创建OpenFeign客户端
java
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
@GetMapping("/getProductById")
ProductInfo getProductById(@RequestParam("id") Integer id);
}
- value:指定目标服务的服务名称
- path:为当前接口中的所有请求方法添加统一的前缀路径(OpenFeign客户端的url需要和服务提供方保持一致)
1.4 远程调用
java
@Service
public class OrderService {
private final OrderMapper orderMapper;
private final ProductApi productApi;
@Autowired
public OrderService(OrderMapper orderMapper, ProductApi productApi) {
this.orderMapper = orderMapper;
this.productApi = productApi;
}
public OrderInfo getOrderById(Integer id) {
OrderInfo orderInfo = orderMapper.selectOrderById(id);
ProductInfo productInfo = productApi.getProductById(orderInfo.getProductId());
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
访问URL:127.0.0.1:8000/order/getOrderById?id=1
返回响应:
javascript
{
"id": 1,
"userId": 2001,
"productId": 1001,
"num": 1,
"price": 99,
"deleteFlag": 0,
"createTime": "2026-02-21T19:46:25.000+00:00",
"updateTime": "2026-02-21T19:46:25.000+00:00",
"productInfo": {
"id": 1001,
"productName": "T恤",
"productPrice": 101,
"status": null,
"createTime": "2026-02-21T19:46:34.000+00:00",
"updateTime": "2026-02-21T19:46:34.000+00:00"
}
}
-
注意1:如果OpenFeign客户端的url与服务提供方不一致,会发生以下报错:

-
注意2:OpenFeign客户端接收不同类型参数时,需要添加对应注解(如下)java@FeignClient(value = "product-service", path = "/product") public interface ProductApi { @GetMapping("/getProductById") ProductInfo getProductById(@RequestParam("id") Integer id); @GetMapping("/demo1") String demo1(@RequestParam("id") Integer id,@RequestParam("name") String name); @GetMapping("/demo2") String demo2(@SpringQueryMap ProductInfo productInfo); @PostMapping("/demo3") String demo3(@RequestBody ProductInfo productInfo); }
2.OpenFeign 最佳实践:继承与抽取
在微服务架构中,使用 OpenFeign 时最大的痛点是 Feign 客户端接口 和 服务提供方 Controller 的代码重复维护。下文将介绍通过 继承 和 抽取公共模块 来解决这一问题
2.1 传统方式痛点分析
java
// 服务提供方
@RestController
@RequestMapping("/product")
@Slf4j
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping("/getProductById")
public ProductInfo getProductById(@RequestParam("id") Integer id){
log.info("getProductById接收到参数,id:{}",id);
return productService.selectProductById(id);
}
@GetMapping("/demo1")
public String demo1(@RequestParam("id") Integer id,@RequestParam("name") String name){
return "id:"+id+",name:"+name;
}
@GetMapping("/demo2")
public String demo2(ProductInfo productInfo){
return productInfo.toString();
}
@PostMapping("/demo3")
public String demo3(@RequestBody ProductInfo productInfo){
return productInfo.toString();
}
}
// Feign 客户端
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
@GetMapping("/getProductById") // 重复
ProductInfo getProductById(@RequestParam("id") Integer id); // 重复
@GetMapping("/demo1") // 重复
String demo1(@RequestParam("id") Integer id,@RequestParam("name") String name); // 重复
@GetMapping("/demo2") // 重复
String demo2(@SpringQueryMap ProductInfo productInfo); // 重复
@PostMapping("/demo3") // 重复
String demo3(@RequestBody ProductInfo productInfo); // 重复
}
- 方法签名、注解、路径定义重复维护
- 修改 API 时容易遗漏同步
2.2 抽取公共模块
-
整体架构

-
项目结构
textspring-cloud-blog/ │ ├── common/ # 公共模块 │ ├── core/ # 核心公共模块 │ │ ├── src/ │ │ │ ├── main/ │ │ │ │ ├── java/org/example/ │ │ │ │ │ ├── api/ │ │ │ │ │ │ └── ProductApi.java # Feign客户端 │ │ │ │ │ └── core/ │ │ │ │ │ ├── OrderInfo.java # 订单信息实体 │ │ │ │ │ └── ProductInfo.java # 产品信息实体 │ ├── modules/ # 业务模块 │ ├── order-service/ # 订单服务 │ │ ├── src/ │ │ │ ├── main/ │ │ │ │ ├── java/org/example/orderservice/ │ │ │ │ │ ├── feign/ │ │ │ │ │ │ └── ProductFeignClient.java # 继承 ProductApi + @FeignClient │ │ │ ├── product-service/ # 产品服务 │ │ ├── src/ │ │ │ ├── main/ │ │ │ │ ├── java/org/example/productservice/ │ │ │ │ │ ├── controller/ │ │ │ │ │ │ └── ProductController.java # 实现ProductApi
2.3 核心代码实现
2.3.1 common-core模块:定义公共接口
java
public interface ProductApi {
@GetMapping("/getProductById")
ProductInfo getProductById(@RequestParam("id") Integer id);
@GetMapping("/demo1")
String demo1(@RequestParam("id") Integer id,@RequestParam("name") String name);
@GetMapping("/demo2")
String demo2(@SpringQueryMap ProductInfo productInfo);
@PostMapping("/demo3")
String demo3(@RequestBody ProductInfo productInfo);
}
2.3.2 product-service模块:实现公共接口
java
@RestController
@RequestMapping("/product")
@Slf4j
public class ProductController implements ProductApi {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping("/getProductById")
public ProductInfo getProductById(@RequestParam("id") Integer id){
log.info("getProductById接收到参数,id:{}",id);
return productService.selectProductById(id);
}
@GetMapping("/demo1")
public String demo1(@RequestParam("id") Integer id,@RequestParam("name") String name){
return "id:"+id+",name:"+name;
}
@GetMapping("/demo2")
public String demo2(ProductInfo productInfo){
return productInfo.toString();
}
@PostMapping("/demo3")
public String demo3(@RequestBody ProductInfo productInfo){
return productInfo.toString();
}
}
2.3.3 order-service模块:Feign客户端继承公共接口
java
@FeignClient(value = "product-service", path = "/product")
public interface ProductFeignClient extends ProductApi {
}
2.3.4 order-service模块:调用ProductFeignClient并注释ProductApi
java
@Service
public class OrderService {
private final OrderMapper orderMapper;
private final RestTemplate restTemplate;
// private final ProductApi productApi;
private final ProductFeignClient productFeignClient;
@Autowired
public OrderService(OrderMapper orderMapper, RestTemplate restTemplate/*, ProductApi productApi*/, ProductFeignClient productFeignClient) {
this.orderMapper = orderMapper;
this.restTemplate = restTemplate;
// this.productApi = productApi;
this.productFeignClient = productFeignClient;
}
public OrderInfo selectOrderById(Integer id) {
OrderInfo orderInfo = orderMapper.selectOrderById(id);
String url = "http://product-service/product/getProductById?id=" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
public OrderInfo getOrderById(Integer id) {
OrderInfo orderInfo = orderMapper.selectOrderById(id);
// ProductInfo productInfo = productApi.getProductById(orderInfo.getProductId());
ProductInfo productInfo = productFeignClient.getProductById(orderInfo.getProductId());
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
public String demo1(Integer id, String name) {
// return productApi.demo1(id, name);
return productFeignClient.demo1(id, name);
}
public String demo2(ProductInfo productInfo) {
// return productApi.demo2(productInfo);
return productFeignClient.demo2(productInfo);
}
public String demo3(ProductInfo productInfo) {
// return productApi.demo3(productInfo);
return productFeignClient.demo3(productInfo);
}
}
访问URL:127.0.0.1:8000/order/getOrderById?id=1
返回响应:
javascript
{
"id": 1,
"userId": 2001,
"productId": 1001,
"num": 1,
"price": 99,
"deleteFlag": 0,
"createTime": "2026-02-21T19:46:25.000+00:00",
"updateTime": "2026-02-21T19:46:25.000+00:00",
"productInfo": {
"id": 1001,
"productName": "T恤",
"productPrice": 101,
"status": null,
"createTime": "2026-02-21T19:46:34.000+00:00",
"updateTime": "2026-02-21T19:46:34.000+00:00"
}
}