【SpringCloud】OpenFeign

在这里插入图片描述

前言

在微服务当中,当一个服务调用另外一个服务的时候,我们的方式是使用 RestTemplate 的方式来调用另外一个服务:

在这里插入图片描述
而使用 RestTemplate 就需要先构造出 URL,如果需要传递的参数很多的话,就需要构造出很复杂的 URL,不仅构造 URL 的时候很容易出错,而且 URL 也会显得很臃肿,那么如何更加优雅的实现服务和服务之间的调用呢?

OpenFeign

OpenFeign 是一个声明式的 Web 服务客户端工具,通常用于简化与 RESTful Web 服务的通信。它可以自动生成 HTTP 请求的客户端代码,使得开发者能够通过接口定义来轻松发起 HTTP 请求。通过 OpenFeign,开发者可以避免手动编写大量的 HTTP 请求相关代码,从而提高开发效率。
就类似 controller 调用 service,只需要创建一个接口,然后添加注解即可使用 OpenFeign。

Feign 是 Netflix 公司开源的一个组件。

  • 2013年6月,Netflix 发布 Feign 的第一个版本 1.0.0
  • 2016年7月,Netflix 发布 Feign 的最后一个版本8.18.0

2016年,Netflix 将 Feign 捐献给社区。

  • 2016年7月 OpenFeign 的首个版本 9.0.0 发布,之后一直持续发布到现在

可以理解为 Netflix Feign 是 OpenFeign 的祖先,或者说 OpenFeign 是 Netflix Feign 的升级版。OpenFeign 是 Feign 的一个更强大更灵活的实现

Spring Cloud Feign

Spring Cloud Feign 是 Spring 对 Feign 的封装,将 Feign 项目集成到 Spring Cloud 生态系统中。并且受 Feign 更名的影响,Spring Cloud Feign 也有两个 starter:

  • spring-cloud-starter-feign
  • spring-cloud-starter-openfeign

而我们使用的就是 spring-cloud-starter-openfeign。OpenFeign 官方文档:https://github.com/OpenFeign/feign,Spring Cloud Feign 文档:https://spring.io/projects/spring-cloud-openfeign

Spring Cloud OpenFeign的使用

引入依赖

首先我们需要在要调用其他服务的服务中添加 openfeign 依赖:

<!--   openfeign     -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

添加注解

当添加完成依赖之后,我们需要在调用其他服务的服务的启动类上添加 @EnableFeignClients 注解,表示开启 OpenFeign 功能。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

编写 OpenFeign 客户端代码

因为服务的具体实现是由其他服务实现的,所以我们在当前服务中就创建一个接口,然后声明出需要调用的服务的方法就可以了:

import org.example.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);
}

我们需要在接口上添加 @FeignClient 注解,其中 value 就是需要调用的微服务的名称,也就是我们在 properties/yml 配置文件中配置的 spring.application.name 服务名称:

在这里插入图片描述
path 则是定义当前 FeignClient 的统一前缀,因为我们的 product-service 服务上添加了类注解 @RequestMapping,所以使用这个 path 就可以避免在当前 FeignClient 的每个方法声明的注解上加上 /product 注解。

这个类除了类型是接口,以及方法没有具体的实现之外,其他的和正常的 Spring MVC 是一样的:

在这里插入图片描述

远程调用

当我们完成上面的配置之后,就可以将 RestTemplate 更换为 OpenFeign 远程调用了:

@Autowired
private ProductApi productApi;

public OrderInfo selectOrderById(Integer id) {
    OrderInfo orderInfo = orderMapper.selectOrderById(id);
    ProductInfo productInfo = productApi.getProductById(orderInfo.getProductId());
    orderInfo.setProductInfo(productInfo);
    return orderInfo;
}

在这里插入图片描述
可以看到,使用 OpenFeign 简化了与 HTTP 服务交互的过程,把 REST 客户端的定义转换为了 Java 接口,并通过注解的方式来声明请求参数,请求方式等信息,使得远程调用更加方便和直接。

上面我们传递的参数数量是一个,并且类型也是 Java 的基本数据类型,那么如果我们要传入多个并且参数类型也不是 Java 的基本数据类型该怎么做呢?

OpenFeign 参数传递

传递单个参数

服务方代码:

@RequestMapping("/product")
@RestController
public class ProductController {
    @Autowired
    private ProductService productService;
   
    @RequestMapping("/p1")
    public String p1(Integer id) {
        return "p1接收到参数:" + id;
    }
}

OpenFeign 客户端代码:

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

注意: 参数绑定的 @RequestParam 不可以省略,但是里面绑定的值是可以省略的。

远程调用方:

@RequestMapping("/feign")
@RestController
public class FeignController {
    @Autowired
    private ProductApi productApi;

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

在这里插入图片描述

传递多个普通类型的参数

当传递多个普通类型的参数的时候,也没什么其他需要注意的,就是只是每个参数需要使用 @RequestParam 进行参数绑定。

服务方 product-service 代码:

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

OpenFeign 客户端代码:

@RequestMapping("/p2")
String p2(@RequestParam("id") Integer id,@RequestParam("name") String name);

远程调用方代码:

@RequestMapping("/o2")
public String o2() {
    return productApi.p2(2,"张三");
}

在这里插入图片描述

传递对象
当远程调用方传递的参数类型是 Java 对象的时候,就不能使用 @RequestParam 注解了,而是需要使用 SpringQueryMap 注解:

服务方 product-service 代码:

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

OpenFeign 客户端代码:

import org.springframework.cloud.openfeign.SpringQueryMap;

@RequestMapping("/p3")
String p3(@SpringQueryMap ProductInfo productInfo);

远程调用代码:

@RequestMapping("/o3")
public String o3() {
    ProductInfo productInfo = new ProductInfo();
    productInfo.setId(1024);
    productInfo.setProductName("李四");
    return productApi.p3(productInfo);
}

在这里插入图片描述

传递JSON
当远程调用方传递的参数的类型是 JSON 类型的话,就需要使用 @RequestBody 来进行参数绑定。

服务方 product-service 代码:

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

OpenFeign 客户端代码:

@RequestMapping("/p4")
String p4(@RequestBody ProductInfo productInfo);

远程调用代码:

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

然后我们调用的话,就使用 postman 来构造 json 请求:

在这里插入图片描述

最佳实践

通过观察 OpenFeign 的客户端和服务提供方的代码我们可以发现;

在这里插入图片描述
在这里插入图片描述
OenFeign 客户端就可以看作是被调用服务的一个接口,这两个部分的代码非常的相近,换句话说,有很多重复的代码,而且不仅仅是重复的代码,比如说 ProductInfo 这个实体类:

在这里插入图片描述
我们在 order-service 这个服务中调用 product-service 这个服务,但是却需要知道 product-service 中的实体类,而微服务中服务和服务之间应该只存在调用和被调用的关系,一个服务是不应该知道另一个服务的具体实现细节的,那么这个 ProductInfo 该如何得到呢?

有人说,我们直接在 order-service 这个服务中引入 product-service jar 包不就可以直接使用 ProductInfo 这个实体类吗?那么你既然引入了 product-service 这个 jar 包,那么product-service 中具体的实现细节你不就全部知道了吗,所以这个方法是肯定行不通的。

那么到底该如何解决这个问题呢?我们可以将公共的代码以及需要用到的实体类全部封装一个公共的 jar 包中,然后这个公共的 jar 包中只存放这些内容,然后我们的服务实现方和服务调用方只需要导入这个公共的 jar 包就可以了。

1.Feign继承方式

我们可以将一些常见的操作封装到一个公共的接口里,然后服务提供方实现这个接口,服务调用方在编写 Feign 接口的时候直接继承这个接口就可以了。

我们可以将接口放在一个公共的 jar 包中,这样服务调用方和服务提供方只需要导入这个 jar 包就可以了:

新建 module

在这里插入图片描述

在新建的 module 下添加需要的依赖 spring boot 和 spring cloud openfeign:

<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>

编写接口:

package org.example.api;

import org.example.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 类:

package org.example.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 包呢?我们可以将这个 jar 包发布到 maven 中央仓库中,然后从中央仓库中导入这个 jar 包,但是 maven 中央仓库中发布内容是比较麻烦的,所以我们可以将 jar 包下载到本地,然后从本地导入这个 jar 包。

在这里插入图片描述
在这里插入图片描述
当我们将提取出来的公共 module 进行打包之后,接下来就在服务提供方和服务调用方引入这个 jar 包:

<dependency>
    <groupId>org.example</groupId>
    <artifactId>product-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

服务实现方实现接口:

package org.example.controller;

import lombok.extern.slf4j.Slf4j;
import org.example.api.ProductInterface;
import org.example.model.ProductInfo;
import org.example.service.ProductService;
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;

@Slf4j
@RequestMapping("/product")
@RestController
public class ProductController implements ProductInterface {
    @Autowired
    private ProductService productService;

    @RequestMapping("/{productId}")
    public ProductInfo getProductById(@PathVariable("productId") Integer productId) {
        log.info("接收到参数: productId"+productId);
        return productService.selectProductById(productId);
    }

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

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

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

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

服务调用方继承接口:

package org.example.api;

import org.springframework.cloud.openfeign.FeignClient;

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

在这里插入图片描述

2. Feign 抽取方式

官方推荐 Feign 的使用方式为继承的方式,但是企业开发中,更多的是把 Feign 接口抽取为一个独立的模块(做法和继承相似,但是理念不同)

如何实现这个做法呢?将 Feign 的 Client 抽取为一个独立的模块,并把涉及到的实体类都放在这个模块中,打成一个 jar 服务。消费方只需要依赖该 jar 包即可,这种方式在企业中比较常见,jar 包通常由服务提供方来实现。

新建一个 module:

在这里插入图片描述
引入依赖:

<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>

编写 API 和实体类:

package org.example.api;

import org.example.model.ProductInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "product-service",path = "/product")
public interface ProductApi{
    @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);
}
package org.example.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;
}

在这里插入图片描述
打包:

在这里插入图片描述
在这里插入图片描述
服务消费方使用 product-api:

首先引入 product-api 依赖:

<groupId>org.example</groupId>
    <artifactId>product-api</artifactId>
    <version>1.0-SNAPSHOT</version>
    <scope>system</scope>
    <systemPath>D:/Maven/.m2/repository/org/example/product-api/1.0-SNAPSHOT/product-api-1.0-SNAPSHOT.jar</systemPath>
</dependency>

然后将我们之前导入的 ProductApi 和 ProudctInfo 的包改为 product-api 下的 ProductApi 和 ProductInfo,然后启动项目:

我们使用这个方法,直接使用 product-api 包下的 Feign-client 的话,项目是启动不了的,还需要添加上 @EnableFeignClients(basePackages = {}) 注解,这个属性指定了Feign客户端接口所在的包路径。

@EnableFeignClients(basePackages = {"com.example1.api"})
@RequestMapping("/order")
@RestController
public class OrderController {
    @Autowired
    private OrderService orderService;

    @Autowired
    private ObjectMapper objectMapper;

    @RequestMapping("/{orderId}")
    public OrderInfo getOrderById(@PathVariable("orderId") Integer orderId) {
        OrderInfo orderInfo = orderService.selectOrderById(orderId);
        return orderInfo;
    }
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不能再留遗憾了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值