SpringCloud 学习笔记3(OpenFeign)

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 。

  1. 我们可以新建一个 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);
    
}
  1. 把当前 jar 包放到本地的 maven 仓库:
  1. 让 order-service、product-service 引入 刚刚打好的 jar 包。
  1. 继承 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 。

  1. 我们可以新建一个 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);
    
}
  1. 把当前 jar 包放到本地的 maven 仓库:
  1. 让 order-service、product-service 引入 刚刚打好的 jar 包。
  1. 由于 Spring 只会扫描启动类下面的目录,而我们要使用的 api 不在启动类下面。所以需要使用@EnableFeignClients(clients = {ProductApi.class}) 来让 Spring 帮我们管理。

    java 复制代码
    package 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);
        }
    }
  2. 服务调用方引入抽取出来的模块

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 仓库,所以程序会启动失败。解决方案如下:

  1. 上传到 maven 仓库,如何发布Jar包到Maven中央仓库 - 简书
  2. 搭建 maven 私服,上传 jar 包到私服。
  3. 从本地读取 jar 包。

在这里选择第三种方法。

  1. 把 jar 包放到本地的 maven 仓库
  1. 更改消费者服务的配置文件

    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设置为 true

    xml 复制代码
            <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。


本文到这里就结束啦~

相关推荐
W.KN6 分钟前
算法日常刷题笔记(5)
笔记
小程同学>o<7 分钟前
嵌入式开发之STM32学习笔记day07
经验分享·笔记·stm32·单片机·嵌入式硬件·学习
eqwaak011 分钟前
实时数仓中的Pandas:基于Flink+Arrow的流式处理方案——毫秒级延迟下的混合计算新范式
大数据·分布式·python·学习·flink·pandas
彬彬131314 分钟前
【C语言】:学生管理系统(多文件版)
c语言·开发语言·经验分享·笔记·学习方法
是懒羊羊吖~1 小时前
【sql靶场】第11、12关-post提交注入
数据库·笔记·sql·post
X Y O1 小时前
opencv初步学习——图像处理3
图像处理·opencv·学习
球求了2 小时前
Linux 入门:权限的认识和学习
linux·运维·服务器·开发语言·学习
zyq~2 小时前
【课堂笔记】定理:样本越多,测量的经验损失越接近真实损失
笔记·机器学习·概率论
宫瑾2 小时前
逻辑派G1 6层高速板学习
学习·fpga开发
charlie1145141912 小时前
IMX6ULL学习整理篇——Linux驱动开发的基础3:向新框架迁移
linux·驱动开发·嵌入式硬件·学习·教程