OpenFeign

目录

问题分析

OpenFeign

快速上手

[OpenFeign 参数传递](#OpenFeign 参数传递)

传递单个参数

传递多个参数

传递对象

[传递 JSON](#传递 JSON)

[OpenFeign 的使用方式](#OpenFeign 的使用方式)

[OpenFeign 继承方式](#OpenFeign 继承方式)

[OpenFeign 抽取方式](#OpenFeign 抽取方式)


问题分析

我们之前使用的远程调用代码:

java 复制代码
@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private RestTemplate restTemplate;
    public OrderInfo findOrderInfoById(Integer orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        String url = "http://product-service/product/" + orderInfo.getProductId();
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}

虽然 RestTemplate 对 HTTP 封装后,比直接使用 HTTPClient简单方便了许多,但也还是存在一些问题:

  1. 需要拼接 URL,灵活性高,但封装臃肿,URL 复杂时,容易出错

  2. 代码可读性差,风格不统一

在Spring Cloud 中,默认使用 HTTP 进行微服务通信,最常用的实现形式有两种:RestTemplate 和 OpenFeign

在本篇文章中,我们就来学习一下 OpenFeign

OpenFeign

OpenFeign 是一个声明式的 Web Service 客户端,它能够让微服务之间的调用变得更简单,类似于 controller 调用 service,只需要创建一个接口,然后添加注解即可使用 OpenFeign

OpenFeign的发展:

Feign 是由 Netflix 开发并开源的一个声明式 HTTP 客户端库。

OpenFeign 是 Feign 项目在 Netflix 将其移交给开源社区维护后(主要在 OpenFeign GitHub 组织下)的延续和发展。

因此,OpenFeign 可以看作是 Feign的官方继承者和现代版本,功能更强、更新更活跃

而 Spring Cloud OpenFeign 则是 Spring Cloud 对 OpenFeign 的封装,将 OpenFeign 项目集成到Spring Cloud 生态系统中,简化 OpenFeign在 Spring 环境中的使用

接下来,我们就来学习一下如何使用 OpenFeign

快速上手

order-service 的 pom.xml 中引入 OpenFeign 依赖:

XML 复制代码
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

在order-service 的启动类 上添加 @EnableFeignClients注解,开启 OpenFeign 功能:

java 复制代码
@EnableFeignClients
@SpringBootApplication
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

添加 ProductApi接口,基于注解来申明远程调用信息:

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

其中:

name/value :指定 FeinClient 名称 (也就是微服务名称),用于服务发现,Feign 底层会使用 Spring Cloud LoadBalance 进行负载均衡

path:当前 FeinClient 的统一前缀

修改远程调用方法:

java 复制代码
@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private ProductApi productApi;
    
    public OrderInfo findOrderInfoById(Integer orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        ProductInfo productInfo = productApi.getProductById(orderInfo.getProductId());
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}

启动product-service 和order-service 服务,访问 127.0.0.1:8080/order/1 接口,测试远程调用:

使用 OpenFeign 也成功实现远程调用,且 OpenFeign 简化了 HTTP 服务交互过程,将 REST 客户端的定义转换为 Java接口,并通过注解的方式来声明请求参数、请求方式等信息,使得远程调用更加方便和间接

接下来,我们继续看 OpenFeign 是如何传递参数的

OpenFeign 参数传递

传递单个参数

服务提供方(product-service):

java 复制代码
@RestController
@RequestMapping("/product")
public class ProductController {
    @RequestMapping("/p1")
    public String p1(Integer id) {
        return "p1 接收到单个参数 id: " + id;
    }
}

OpenFeign 客户端:

java 复制代码
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
    @RequestMapping("/p1")
    String p1(@RequestParam("id") Integer id);
}

服务消费方(order-service):

java 复制代码
@RequestMapping("/feign")
@RestController
public class OpenFeignController {
    @Autowired
    private ProductApi productApi;

    @RequestMapping("/o1")
    public String o1(Integer id) {
        return productApi.p1(id);
    }
}

访问 127.0.0.1:8080/feign/o1?id=1 接口,测试远程调用:

传递多个参数

与传递单个参数基本类似,只不过需要使用多个 @RequestParam 来进行参数绑定

服务提供方(product-service):

java 复制代码
@RestController
@RequestMapping("/product")
public class ProductController {
    @RequestMapping("/p2")
    public String p2(Integer id, String name) {
        return "p2 接收到多个参数 id: " + id + " name: " + name;
    }
}

OpenFeign 客户端:

java 复制代码
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
    @RequestMapping("/p2")
    String p2(@RequestParam("id") Integer id, @RequestParam("name") String name);
}

服务消费方(order-service):

java 复制代码
@RequestMapping("/feign")
@RestController
public class OpenFeignController {
    @Autowired
    private ProductApi productApi;

    @RequestMapping("/o2")
    public String o2(Integer id, String name) {
        return productApi.p2(id, name);
    }
}

访问 127.0.0.1:8080/feign/o2?id=1&name=zhangsan 接口,测试远程调用:

传递对象

服务提供方(product-service):

java 复制代码
@RestController
@RequestMapping("/product")
public class ProductController {
    @RequestMapping("/p3")
    public String p3(ProductInfo productInfo) {
        return "p3 接收到对象 productInfo: " + productInfo;
    }
}

OpenFeign 客户端:

java 复制代码
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
    @RequestMapping("/p3")
    String p3(@SpringQueryMap ProductInfo productInfo);
}

服务消费方(order-service):

java 复制代码
@RequestMapping("/feign")
@RestController
public class OpenFeignController {
    @Autowired
    private ProductApi productApi;

    @RequestMapping("/o3")
    public String o3(ProductInfo productInfo) {
        return productApi.p3(productInfo);
    }
}

访问 127.0.0.1:8080/feign/o3?id=1&productName=zhangsan 接口,测试远程调用:

传递 JSON

服务提供方(product-service):

java 复制代码
@RestController
@RequestMapping("/product")
public class ProductController {
    @RequestMapping("/p4")
    public String p4(@RequestBody ProductInfo productInfo) {
        return "p4 接收到 JSON 对象: " + productInfo;
    }
}

OpenFeign 客户端:

java 复制代码
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
    @RequestMapping("/p4")
    String p4(@RequestBody ProductInfo productInfo);
}

服务消费方(order-service):

java 复制代码
@RequestMapping("/feign")
@RestController
public class OpenFeignController {
    @Autowired
    private ProductApi productApi;

    @RequestMapping("/o4")
    public String o4(@RequestBody ProductInfo productInfo) {
        return productApi.p4(productInfo);
    }
}

访问 http://127.0.0.1:8080/feign/o4 接口,测试远程调用:

从上述参数传递代码中,我们可以发现 OpenFeign 客户端和服务提供者的 controller 是非常相似的:

且 ProductInfo 在 order-service 和 product-service 中重复创建

那么,我们是否可以简化这种写法呢?

OpenFeign 的使用方式

OpenFeign 继承方式

OpenFeign 支持继承的方式,我们可以将一些常用的操作封装在接口中

定义好一个接口,服务提供方实现这个接口,服务消费方编写 OpenFeign 接口时,直接继承这个接口即可

我们创建一个公共的 jar 包,供服务提供方和服务消费方使用(后面还会学习抽取方式,可以将目前代码复制一份,以便后续使用):

引入依赖:

XML 复制代码
    <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 和 ProductInfo 复制到product-api模块中:

ProductInterface:

java 复制代码
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:

java 复制代码
@Data
public class ProductInfo {
    private Integer id;
    private String productName;
    private Integer productPrice;
    private Integer state;
    private Date createTime;
    private Date updateTime;
}

使用 Maven 打包:

观察本地 Maven仓库中 jar 包是否成功打包:

服务提供方(product-service) 引入product-api 依赖:

XML 复制代码
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>product-api</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>

实现 ProductInterface接口:

java 复制代码
@RestController
@RequestMapping("/product")
public class ProductController implements ProductApi {
    @Autowired
    private ProductService productService;

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

    @RequestMapping("/p1")
    public String p1(Integer id) {
        return "p1 接收到单个参数 id: " + id;
    }

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

    @RequestMapping("/p3")
    public String p3(ProductInfo productInfo) {
        return "p3 接收到对象 productInfo: " + productInfo;
    }

    @RequestMapping("/p4")
    public String p4(@RequestBody ProductInfo productInfo) {
        return "p4 接收到 JSON 对象: " + productInfo;
    }
}

服务消费方(order-service) 同样引入依赖,并继承 ProductApi:

java 复制代码
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi extends ProductInterface {
}

启动服务,访问 127.0.0.1:8080/order/1 接口:

除了继承方式,也可以把 OpenFeign 接口抽取为一个独立的模块

OpenFeign 抽取方式

OpenFeign 抽取:将 OpenFeign 的 Client 抽取为一个独立的模块,并将涉及到的实体类等都放到这个模块中,服务消费方只需要添加该 jar 包即可,这个 jar 包通常由服务提供方实现

同样创建一个 model:

引入依赖:

XML 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

将 ProductApi 、ProductInfo 复制到product-api模块中:

ProductInfo:

java 复制代码
@Data
public class ProductInfo {
    private Integer id;
    private String productName;
    private Integer productPrice;
    private Integer state;
    private Date createTime;
    private Date updateTime;
}

ProductApi:

java 复制代码
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
    @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);
}

删除order-service 中的ProductApi、ProductInfo,并引入依赖:

XML 复制代码
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>product-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

修改 order-service 中的 ProductApi 、ProductInfo 的路径为product-api 中路径

在启动类中添加扫描路径:

java 复制代码
@EnableFeignClients(basePackages = {"com.example.api"})
@SpringBootApplication
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

启动服务,并测试远程调用 127.0.0.1:8080/order/1:

相关推荐
337805 分钟前
5分钟搞懂登录认证和授权框架Spring Security
java·spring boot
vipxieliang6 分钟前
Spring Security 6.x 实战:认证、授权与 JWT 集成
java·spring boot
world78112269 分钟前
【C++】set 和 map
java·前端·c++·set·map
学长毕设9 分钟前
基于SpringBoot的社区体育馆预约系统的设计与实现
java·spring boot·后端
知已难寻10 分钟前
第 5 篇 · 云原生生态与实践:微服务、DevOps、可观测性与 Serverless
大数据·linux·服务器·微服务·云原生·云计算·devops
Rain的Java大神之路40 分钟前
100 亿条数据如何快速写入 MySQL?从 231 天到 3.5 小时,我把面试官问懵了
java·后端·面试
xyouyuweizhu1 小时前
Java IO 流详解:流的作用、流的划分与标准输入输出流
java·python·php
利威尔xu1 小时前
Go IM 源码实战(7):断线三天重连,水位算错就漏消息
redis·后端·微服务·面试·golang
范同学~1 小时前
项目添加水印
java·前端·javascript
快乐的韭菜根根1 小时前
Slurm 指定 GPU 卡:`File=/dev/nvidiaX` 映射的坑与修复
java·服务器·网络