优雅的实现服务调用 -- OpenFeign

1. RestTemplate存在问题

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

@Autowired
private RestTemplate restTemplate;
public OrderInfo getOrderById(int id) {
    OrderInfo orderInfo = orderMapper.getOrderById(id);
    String url = "http://product-service/product/getProductById?id=" + orderInfo.getProductId();
    ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
    orderInfo.setProductInfo(productInfo);
    return orderInfo;
}
  • URL需要拼接,灵活性高,但是封装臃肿,容易出错
  • 代码可读性差,风格不统一

微服务之间的通信,通常有两种:RCP和HTTP

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

  • RestTemplate
  • OpenFeign

2. OpenFeign介绍

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

Feign是Netflix公司的一个组件,16年Netflix将Feign捐献给社区,2016年7月OpenFeign的首个版本9.0.0发布

可以理解为 Netflix Feign是Open Feign的祖先,或者说OpenFeign是NetFlix Feign的升级版

Spring Cloud Feign是Spring 对 Feign的封装,将Feign项目集成到Spring Cloud生态系统中

3. 快速上手

引入依赖

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

添加注解

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

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

编写OpenFeign的客户端

基于SpringMVC的注解来声明远程调用的信息

@FeignClient(value = "product-service",path = "/product/getProductBuId")
public interface ProductApi {
    @RequestMapping("/{id}")
    ProductInfo getProductBuId(@PathVariable("id") Integer id);
}

参数说明:

  • name/value:指定FeignClient的名称,也就是微服务的名称,用于服务发现.Feign底层会使用Spring Cloud LoadBalance进行负载均衡,也可以使用url属性指定一个具体的url
  • path:定义当前FeignClient的统一前缀

远程调用

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private ProductApi productApi;
    @Autowired
    private RestTemplate restTemplate;
    public OrderInfo getOrderById(int id) {
        OrderInfo orderInfo = orderMapper.getOrderById(id);
//        String url = "http://product-service/product/getProductById?id=" + orderInfo.getProductId();
        ProductInfo productInfo = productApi.getProductById(orderInfo.getProductId());
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}

Feign 简化了与HTTP服务交互的过程, 把REST客户端的定义转换为Java接⼝, 并通过注解的⽅式来声

明请求参数,请求⽅式等信息, 使远程调⽤更加方便和间接.

4. OpenFeign参数传递

从URL中获取参数

服务提供方product-service

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

OpenFeign客户端

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

服务消费方order-service

@RestController
@RequestMapping("testOpenFeign")
public class TestFeignController {
    @Autowired
    private ProductApi productApi;
    @RequestMapping("/o1")
    public String o1(Integer id) {
        return productApi.p1(id);
    }
}

测试:

传递单个参数

服务提供方product-service

@RequestMapping("/p2")
public String p2(@RequestParam("id") int id) {
    return "p2接受到参数" + id;
}

OpenFeign客户端

@RequestMapping("/p2")
String p2 (@RequestParam("id") Integer id);// 这里的@RequestParam做参数绑定,不能省略

服务消费方order-service

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

测试:

传递多个参数

需要使用多个@RequestParam进行参数绑定

服务提供方product-service

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

OpenFeign客户端

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

服务消费方order-service


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

测试:

传递对象

服务提供方product-service

    @RequestMapping("/p4")
    public String p4(ProductInfo productInfo) {
        return "接收到参数" + productInfo;
    }

OpenFeign客户端

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

服务消费方order-service

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

测试:

传递JSON

服务提供方product-service

     @RequestMapping("/p5")
    public String p5(@RequestBody ProductInfo productInfo) {
        return "接收到参数" + productInfo;
    }

OpenFeign客户端

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

服务消费方order-service


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

测试:

在这里插入图片描述

5. 最佳实践

不难发现,Feign的客户端代码和服务提供者的代码有很大的相似性

提出两种解决方法

Feign继承方式

我们可以将一些常见的操作封装到接口里,服务提供方实现这个接口,服务消费方编写Feign接口的时候,直接继承这个接口

创建一个新的模块

将接口放在一个公共的jar包里面,供服务提供方和服务消费方使用

引入依赖

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

编写接口

public interface ProductInterface {
    @RequestMapping("/getProductById")
    ProductInfo getProductById(@RequestParam("id") Integer id);

    @RequestMapping("/p1/{id}")
    String p1 (@PathVariable("id") Integer id);


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

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

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

    @RequestMapping("/p5")
    String p5 (@RequestBody ProductInfo productInfo);
}

注意:这里的ProductInfo需要在product-api抽取出来,因此可以将order-service以及product-service里面的ProductInfo删除

打jar包

此时就会将product-api的jar包放到本地的maven仓库,供product-service以及order-service使用

服务实现方实现ProductApi接口

package org.JWCB.controller;

import org.JWCB.api.ProductInterface;
import org.JWCB.model.ProductInfo;
import org.JWCB.service.ProductService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RequestMapping("product")
@RestController
public class ProductController implements ProductInterface {
    private static final Logger log = LoggerFactory.getLogger(ProductController.class);
    @Autowired
    private ProductService productService;
    @RequestMapping("getProductById")
    public ProductInfo getProductById(@RequestParam Integer id) {
        log.info("getProductById");
        return productService.getProductById(id);
    }


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

    @RequestMapping("/p2")
    public String p2(@RequestParam("id") Integer id) {
        return "p2接受到参数" + id;
    }

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

    @RequestMapping("/p4")
    public String p4(ProductInfo productInfo) {
        return "接收到参数" + productInfo;
    }

    @RequestMapping("/p5")
    public String p5(@RequestBody ProductInfo productInfo) {
        return "接收到参数" + productInfo;
    }
}

此时需要将本地Maven仓库的ProductApi导入,可以先将接口名完整打出来,由idea自己导入

<dependency>
  <groupId>org.JWCB</groupId>
  <artifactId>product-api</artifactId>
  <version>1.0-SNAPSHOT</version>
  <scope>compile</scope>
</dependency>

注意:这里的ProductInfo是来自于product-api这个包

服务消费方继承接口

package org.JWCB.api;
import org.JWCB.model.ProductInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.*;

@FeignClient(value = "product-service",path = "/product")
public interface ProductApi extends ProductInterface{
    @RequestMapping("/getProductById")
    ProductInfo getProductById(@RequestParam("id") Integer id);

    @RequestMapping("/p1/{id}")
    String p1 (@PathVariable("id") Integer id);


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

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

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

    @RequestMapping("/p5")
    String p5 (@RequestBody ProductInfo productInfo);
}

测试:

Feign抽取方式

做法实际上与继承的方式相似,但是理念不同,这种方式是将Feign抽取成一个独立的模块

就是将Feign的Client抽取为一个独立的模块,并把涉及的实体类都放入这个模块里面,打成一个jar包. 服务消费方只需要依赖该jar包即可

创建一个module

引入依赖

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

编写API

@FeignClient(value = "product-service",path = "/product")
public interface ProductInterface {
    @RequestMapping("/getProductById")
    ProductInfo getProductById(@RequestParam("id") Integer id);

    @RequestMapping("/p1/{id}")
    String p1 (@PathVariable("id") Integer id);


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

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

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

    @RequestMapping("/p5")
    String p5 (@RequestBody ProductInfo productInfo);
}

打jar包

服务消费方使用接口

删除ProductApi以及ProductInfo

引入依赖:

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

修改order-service代码

指定扫描类

测试

在这里插入图片描述

6. 服务部署到Linux注意事项

使用Maven进行打包的时候.默认是从远程中央仓库进行下载的,但是product-api这个包在本地,我们可以制定从本地读取jar包

修改pow.xml文件

<dependency>
  <groupId>org.JWCB</groupId>
  <artifactId>product-api</artifactId>
  <version>1.0-SNAPSHOT</version>
  <scope>system</scope>
  <systemPath>C:/Users/31127/.m2/repository/org/JWCB/product-api/1.0-SNAPSHOT/product-api-1.0-SNAPSHOT.jar</systemPath>
</dependency>


<plugins>
  <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
      <includeSystemScope>true</includeSystemScope>
    </configuration>
  </plugin>
</plugins>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值