Spring中OpenFeign的使用方法最佳实践
作者:LileSily
1. RestTemplate存在的问题
观察我们之前远程调用的代码
public OrderInfo selectOrder(Integer id){ OrderInfo orderInfo = orderMapper.selectOrderById(id); //把获取到的服务URL拼接到远程调用的URL中 String url = "http://product-service/product/select?id=" + orderInfo.getProductId(); ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class); orderInfo.setProductInfo(productInfo); return orderInfo; }
虽然说RestTemplate对HTTP封装之后,已经比直接使用HTTP Client方便很多,但是还是存在一些问题的.
- 需要拼接URL,灵活性高,但是封装臃肿,URL复杂的时候,容易出错.
- 代码可读性差,风格不统一.
微服务之间的通行主要有两种方式,一种是RPC,一种是HTTP.在SpringCloud中,默认是使用HTTP进行通信的,最常用的实现形式有两种:
- RestTemplate
- OpenFeign
接下来我们就来详细介绍一下OpenFeign.
2. OpenFeign介绍
OpenFeign是一个声明式的Web Service客户端.它让微服务之间的调用变得更加简单,类似与Controller调用Service,只需要创建一个接口,然后再添加注解就可以使用OpenFeign.
2.1 引入依赖
首先我们需要在服务调用方(order-service)中引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
2.2 添加注解
在order-service的启动类添加注解,@EnableFeignClients
,开启OpenFeign的功能.
@SpringBootApplication @EnableFeignClients public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class,args); } }
2.3 编写OpenFeign客户端
在调用其他服务的客户端之下,编写API调用接口,基于SpringMVC的注解来声明远程调用的信息.
@FeignClient(name = "product-service") public interface ProductApi { @RequestMapping("/product/select") ProductInfo getProductInfo(@RequestParam("id")Integer id); }
[注意]:在接口的参数前面必须使用@RequestParam加上所调用服务接口的对应参数,否则参数无法传入,就相当于在请求的后面加上了?参数=xxx
.
也可以把该服务的接口的统一前缀(放在类上的@RequestMapping
)统一放到FeignClient注解的Path参数中.
@FeignClient(name = "product-service",path = "/product") public interface ProductApi { @RequestMapping("/select") ProductInfo getProductInfo(@RequestParam("id")Integer id); }
@FeignClient
注解作用在接口上,参数说明:
- name/value: 指定OpenFeign的名称,也就是被调用的微服务的名称,用于服务发现,Feign底层会使用SpringLoadBalance进行负载均衡.
当然也可以使用URL属性指定一个具体的URL. - Path: 定义当前FeignClient的统一前缀,一般是被调用方Controller层的类注解.
2.4 远程调用
修改远程调用方法
@Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private ProductApi api; //设置原子类 public OrderInfo selectOrder(Integer id){ OrderInfo orderInfo = orderMapper.selectOrderById(id); //把获取到的服务URL拼接到远程调用的URL中 Integer productId = orderInfo.getProductId(); ProductInfo productInfo = api.getProductInfo(productId); orderInfo.setProductInfo(productInfo); return orderInfo; } }
2.5 测试
启动服务.访问接口,测试远程调用.
3. OpenFeign参数传递
3.1 传递单个参数
我们上面2.4的例子就是单个参数的调用.下面我们再来举一个例子
- 服务提供方product-service
@RestController @RequestMapping("/product") public class ProductController { @RequestMapping("/p1") public String p1(Integer id){ return "接收到参数"+id; } }
- FeginClient
@FeignClient(value = "product-service") public interface ProductApi { @RequestMapping("/product/p1") String p1(@RequestParam("id") Integer id); }
和我们上面提到的一样,@RequestParam是用来做参数绑定的,不能省略.
3. order-service
@RequestMapping("/order") @RestController public class OrderController { @Autowired private ProductApi api; @RequestMapping("/p1") public String p1(Integer id){ return api.p1(id); } }
远程测试调用:GET http://127.0.0.1:8080/order/p1?id=1
3.2 传递多个参数
使用多个@RequestParam
进行参数绑定即可
- 服务提供方product-service
@RestController @RequestMapping("/product") public class ProductController { @RequestMapping("/p2") public String p2(Integer id1,Integer id2){ return "接收到参数1:" + id1 + ",接收到参数2:" + id2; } }
- FeignClient
@FeignClient(value = "product-service") public interface ProductApi { @RequestMapping("/product/p2") String p2(@RequestParam("id1") Integer p1,@RequestParam("id2") Integer p2); }
- 服务消费方order-service
@RequestMapping("/order") @RestController public class OrderController { @Autowired private ProductApi api; @RequestMapping("/p2") public String p2(Integer p1,Integer p2){ return api.p2(p1,p2); } }
调用接口进行测试:GET http://127.0.0.1:8080/order/p2?p1=1&p2=2
4.3 传递对象
- 服务提供方product-service
@RequestMapping("/p3") public String p3(ProductInfo productInfo){ return "接收到对象:" + productInfo; }
- Feign
@RequestMapping("/product/p3") String p3(@SpringQueryMap ProductInfo productInfo);
其中@SpringQueryMap
注解表示的是:可以方便地将一个对象的属性作为请求的查询参数添加到请求的 URL中,避免了手动构建查询参数字符串(如json)的繁琐过程.
3. order-service
@RequestMapping("/p3") public String p3(ProductInfo productInfo){ return api.p3(productInfo); }
测试远程调用:GET http://127.0.0.1:8080/order/p3?id=5&productName=zhangsan
3.4 传递Json
和我们之前学习SpringBoot的时候一样,同样是使用@RequestBody
注解进行绑定.
注意由于数据在服务之间一直是按照Json格式传递的,所以服务方,服务调用方,FeignClient的参数上都需要加上注解.
- 服务提供方product-service
@RequestMapping("/p4") public String p4(@RequestBody ProductInfo productInfo){ return "接收到对象:" + productInfo; }
- FeignClient
@RequestMapping("/product/p4") String p4(@RequestBody ProductInfo productInfo);
- 服务消费方order-service
@RequestMapping("/p4") public String p4(@RequestBody ProductInfo productInfo){ return api.p4(productInfo); }
测试远程调用:http://127.0.0.1:8080/order/p4
4. 最佳实践
通过观察,我们也能看出来,Feign的客户端与服务提供者的controller代码非常相似.
Feign客户端:
@FeignClient(value = "product-service") public interface ProductApi { @RequestMapping("/product/p1") String p1(@RequestParam("id") Integer id); }
服务提供方Controller
@RestController @RequestMapping("/product") public class ProductController { @RequestMapping("/p1") public String p1(Integer id){ return "接收到参数"+id; } }
有没有一种方法可以简化这种写法呢?
4.1 Feign继承方式
Feign支持继承的方式,我们可以把一些常见的操作封装到接口里.
我可以定义好一个接口,服务提供方实现这个接口,服务消费方编写Feign接口的时候,直接继承这个接口.
接口可以放在一个公共的jar包中,供服务提供方和服务消费方使用.
- 新建一个模块
- 引入依赖
<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>
- 编写接口
复制ProductInfo类和ProductApi到product-api模块中.把ProductApi改名为ProductInterface - 打jar包,并安装到本地Maven仓库
点击install.该模块就会被安装到本地的Maven仓库.
由于jar包中已经有了ProductInfo类,所以我们就可以把product-service和order-service的ProductInfo类注掉了. - 服务提供方
需要先在服务方中引入对应模块的依赖.
<dependency> <groupId>org.example</groupId> <artifactId>product-api</artifactId> <version>1.0-SNAPSHOT</version> <scope>compile</scope> </dependency>
服务方product-service
直接实现接口ProductInterface
.
@RestController @RequestMapping("/product") public class ProductController implements ProductInterface { @Autowired private ProductService productService; @RequestMapping("/select") public ProductInfo getProductInfo(Integer id){ return productService.selectById(id); } @RequestMapping("/p1") public String p1(Integer id){ return "接收到参数"+id; } @RequestMapping("/p2") public String p2(Integer id1,Integer id2){ return "接收到参数1:" + id1 + ",接收到参数2:" + id2; } @RequestMapping("/p3") public String p3(ProductInfo productInfo){ return "接收到对象:" + productInfo; } @RequestMapping("/p4") public String p4(@RequestBody ProductInfo productInfo){ return "接收到对象:" + productInfo; } }
- 服务消费方
同样,首先先添加依赖.
之后让FeignClient继承ProductInterface
@FeignClient(value = "product-service") public interface ProductApi extends ProductInterface { }
注意在上述步骤中,把ProductInfo类引入包的路径全部改为之前打包的product-api模块下ProductInfo类的路径.
- 测试接口
http://127.0.0.1:8080/order/select?id=1
4.2 Feign抽取方式
官方推荐Feign的使用方式为继承的方式,但是企业开发中,更多是把Feign接口抽取为⼀个独立的模块(做法和继承相似,但理念不同).
操作方法:
将Feign的Client抽取为⼀个独立的模块,并把涉及到的实体类等都放在这个模块中,打成⼀个Jar.服务消费方只需要依赖该Jar包即可.这种方式在企业中比较常见,Jar包通常由服务提供方来实现.
前4步和前面继承的方式一样.只不过接口ProductApi在复制到新模块的时候不用修改名字
- 服务消费方使用product-api
首先引入依赖,之后把该模块中原有的ProductApi接口注掉.
<dependency> <groupId>org.example</groupId> <artifactId>product-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
之后指定扫描类
在启动类中添加需要加载的Feign客户端.
@SpringBootApplication @EnableFeignClients(clients = ProductApi.class) public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class,args); } }
- 测试
远程调用接口http://127.0.0.1:8080/order/select?id=1
总结
到此这篇关于Spring中OpenFeign的使用方法的文章就介绍到这了,更多相关Spring OpenFeign的使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!