Fegin介绍

Fegin是一个非常好用的HTTP客户端

Feign很大程度上简化了HTTP调用方式

Fegin能做什么

Feign包含了多种HTTP的调用形式

(1 、Spring MVC: @RequestMapping  @RequestParam @Pathvariable, @RequestHeader, @RequestBody)

Feign可以整合Ribbon和Hystrix

Feign提供了多种HTTP底层支持(底层支持Apache HttpClient,OKHttp,RestTemplate等)

Feign特性

Feign实现了可插拔注解支持,包括Feign和JAX-RS注解

Feign支持可插拔的HTTP编码器和解码器

Feign支持HTTP请求和响应的压缩

传统服务间的调用方式: 比如服务A调用服务B,服务A要跟服务B建立网络连接,然后构造一个复杂的请求,最后对返回的响应结果再写一大堆代码来处理。

Feign为我们提供了优雅的解决方案: 用注解定义一个FeignClient接口。没有底层建立连接,构造请求,解析响应的代码。Fegin底层会根据注解,和你指定的服务建立连接,构造请求,获取响应,解析响应等等。

Feign原理: Fegin的一个关键机制是使用了动态代理,如下图: 参考 https://blog.csdn.net/Anbang713/article/details/85370080

首先,如果你对某个接口定义了 @FeignClient注解,Fegin就会针对这个接口创建一个动态代理

接着你要调用哪个接口,本质就是调用Fegi创建的动态代理,这是核心的核心

Fegin的动态代理会根据你在接口上的 @RequestMmapping等注解,来动态构造你要请求的服务地址

最后针对这个地址,发起请求、解析响应

Feign实践

通过Order工程调用Product工程的接口

一、Feign实现应用间的通信

声明式REST客户端(伪RPC),采用基于接口的注解。本质上是Http客户端,Http远程调用。

1、 在Order工程中的pom文件增加

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

  

2、增加注解@EnableFeignClients

 

3、声明要调用的接口

package com.example.demo.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping; /**
* 需要在Product服务中要调的接口
*/
@FeignClient(name = "product") //product代表访问product应用下的msg接口
public interface ProductClient { @GetMapping("/msg") //
String productMsg();
}

  

4、在Order应用调用

二、Order查询调用Product来查询商品信息

第一步:Product工程

1、增加  List<ProductInfo> findByProductIdIn(List<String> productIdList);方法

package com.example.product.repository;

import com.example.product.dataobject.ProductInfo;
import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface ProductInfoRepository extends JpaRepository<ProductInfo, String> {
//查询所有在架的商品
List<ProductInfo> findByProductStatus(Integer productStatus); List<ProductInfo> findByProductIdIn(List<String> productIdList);
}

  

2、Server层

@Service
public class ProductServiceImpl implements ProductService{ @Autowired
private ProductInfoRepository productInfoRepository; /**
* 查询商品列表
*
* @param productIdList
*/
@Override
public List<ProductInfo> findList(List<String> productIdList) {
return productInfoRepository.findByProductIdIn(productIdList);
}
}

接口

public interface ProductService {

    /**
* 查询商品列表
* @param productIdList
*/
List<ProductInfo> findList(List<String> productIdList);
}

  

3、Controller层

@RestController
@RequestMapping("/product")
@Slf4j
public class ProductController { @Autowired
private ProductService productService; /**
* 获取商品列表(给订单服务用)
* @param productIdList
* @return
*/
@PostMapping("/listForOrder")
public List<ProductInfo> listForOrder(@RequestBody List<String> productIdList){
return productService.findList(productIdList);
}
}

  

第二步、Order工程

1.在接口中定义product中的方法

/**
* 需要在Product服务中要调的接口
*/
@FeignClient(name = "product") //product代表访问product应用下的msg接口
public interface ProductClient { @PostMapping("/product/listForOrder")
List<ProductInfo> listForOrder(@RequestBody List<String> productIdList);
}

  

2、Controller层调用上一步定义的方法。

@RestController
@Slf4j
public class ClientController { @Autowired
private ProductClient productClient; @GetMapping("/getProductList")
public String getProductList(){
List<ProductInfo> productInfoList = productClient.listForOrder(Arrays.asList("164103465734242707"));
log.info("response={}",productInfoList);
return "ok";
}
}

  

  

三、扣库存的实现

第一步:Product工程

1、创建ResultEnum 信息提示

@Getter
public enum ResultEnum {
PRODUCT_NOT_EXIST(1,"商品不存在"), PRODUCT_STOCK_ERROR(2,"库存有误"),
; private Integer code;
private String message; ResultEnum(Integer code, String message){
this.code = code;
this.message = message;
}
}

 

2、创建购物车类CartDTO

/**
* 购物车
*/
@Data
public class CartDTO {
/**
* 商品id
*/
private String productId; /**
* 商品数量
*/
private Integer productQuantity; public CartDTO() { } public CartDTO(String productId, Integer productQuantity) {
this.productId = productId;
this.productQuantity = productQuantity;
}
}

  

  3、异常类

public class ProductException extends  RuntimeException {
private Integer code; public ProductException(Integer code, String message){
super(message);
this.code = code;
} public ProductException(ResultEnum resultEnum){
super(resultEnum.getMessage());
this.code = resultEnum.getCode();
}
}

  

4、Service层扣库存

   /**
* 扣库存
*
* @param cartDTOList
*/
@Override
@Transactional //由于扣库存是list操作,所以需要事务操作
public void decreaseStock(List<CartDTO> cartDTOList) {
for(CartDTO cartDTO : cartDTOList){
Optional<ProductInfo> productInfoOptional = productInfoRepository.findById(cartDTO.getProductId());
//判断商品是否存在
if(!productInfoOptional.isPresent()){
throw new ProductException(ResultEnum.PRODUCT_NOT_EXIST);
}
ProductInfo productInfo = productInfoOptional.get();
//库存是否足够 数据库里的库存-购物车中的数量
Integer result = productInfo.getProductStock() - cartDTO.getProductQuantity();
if(result <= 0){
throw new ProductException(ResultEnum.PRODUCT_STOCK_ERROR);
}
productInfo.setProductStock(result);
productInfoRepository.save(productInfo);
}
}

  

5、Controller层

   @PostMapping("/decreaseStock")
public void decreaseStock(@RequestBody List<CartDTO> cartDTOList){
productService.decreaseStock(cartDTOList);
}

  

第二步 Order工程

1、增加方法定义

/**
* 需要在Product服务中要调的接口
*/
@FeignClient(name = "product") //product代表访问product应用下的msg接口
public interface ProductClient { @PostMapping("/product/decreaseStock")
void decreaseStock(@RequestBody List<CartDTO> cartDTOList);
}

  

2、增加测试Controller

  @GetMapping("/productDecreaseStock")
public String productDecreaseStock(){
productClient.decreaseStock(Arrays.asList(new CartDTO("164103465734242707",3)));
return "ok";
}

 

3、测试调用

然后查看数据库是否扣库存成功。

 

四、完善下单接口

1、服务层代码

 @Autowired
private ProductClient productClient; /**
* 创建订单
*
* @param orderDTO
* @return
*/
@Override
public OrderDTO create(OrderDTO orderDTO) {
String orderId= KeyUtil.genUniqueKey();
// 查询商品信息(调用商品服务)
List<String> productIdList = orderDTO.getOrderDetailList().stream()
.map(OrderDetail::getProductId)
.collect(Collectors.toList());
List<ProductInfo> productInfoList = productClient.listForOrder(productIdList);
Date date = new Date();
//计算总价
BigDecimal orderAmount = new BigDecimal(0);
for(OrderDetail orderDetail: orderDTO.getOrderDetailList()){
for(ProductInfo productInfo : productInfoList){
if (productInfo.getProductId().equals(orderDetail.getProductId())){
//单价 * 数量
orderAmount = productInfo.getProductPrice()
.multiply( new BigDecimal(orderDetail.getProductQuantity()))
.add(orderAmount);
//这种方式copy,值为null也会copy过去
BeanUtils.copyProperties(productInfo, orderDetail);
orderDetail.setOrderId(orderId);
orderDetail.setDetailId(KeyUtil.genUniqueKey());
orderDetail.setCreateTime(date);
orderDetail.setUpdateTime(date);
//订单详情入口
orderDetailRepository.save(orderDetail);
}
} } //扣库存(调用商品服务)
List<CartDTO> cartDTOList = orderDTO.getOrderDetailList().stream()
.map(e -> new CartDTO(e.getProductId(), e.getProductQuantity()))
.collect(Collectors.toList());
productClient.decreaseStock(cartDTOList); // 5、订单入口
OrderMaster orderMaster = new OrderMaster();
orderDTO.setOrderId(orderId);
BeanUtils.copyProperties(orderDTO, orderMaster);
orderMaster.setOrderAmount(orderAmount);
orderMaster.setOrderStatus(OrderStatusEnum.New.getCode());
orderMaster.setPayStatus(PayStatusEnum.WAIT.getCode());
orderMaster.setCreateTime(date);
orderMaster.setUpdateTime(date);
orderMasterResponsibility.save(orderMaster); return orderDTO;
}

  

2、Controller层代码

    @RequestMapping("/create")
public ResultVO<Map<String, String>> create(@Valid OrderForm orderForm, BindingResult bindingResult){
if(bindingResult.hasErrors()){
log.error("【创建订单】参数不正确, orderForm={}", orderForm);
throw new OrderException(ResultEnum.PARAM_ERROR.getCode(),
bindingResult.getFieldError().getDefaultMessage());
}
// orderForm -> orderDTO
OrderDTO orderDTO = OrderForm2OrderDTOConverter.convert(orderForm);
if (CollectionUtils.isEmpty(orderDTO.getOrderDetailList())) {
log.error("【创建订单】购物车信息为空");
throw new OrderException(ResultEnum.CART_EMPTY);
} OrderDTO result = orderService.create(orderDTO); Map<String, String> map = new HashMap<>();
map.put("orderId", result.getOrderId());
return ResultVOUtil.success(map);
}

  

3、验证下单接口,使用postman工具

Feign实践缺点:

里面介绍到Order工程调用Product工程里的方法,定义了FeignClient的/product/listForOrder方法,如果还有一个工程要调用/product/listForOrder这个方法,又要重复定义一次。

/

解决方法:

可以把Feign的接口定义统一到一个工程中,其它接口如果想调用,继承这个接口即可。

举例:

定义了一个Backend_API工程

然后在要使用这个接口的地方继承该接口

Feign的使用的更多相关文章

  1. Spring Cloud 声明式服务调用 Feign

    一.简介 在上一篇中,我们介绍注册中心Eureka,但是没有服务注册和服务调用,服务注册和服务调用本来应该在上一章就应该给出例子的,但是我觉得还是和Feign一起讲比较好,因为在实际项目中,都是使用声 ...

  2. Feign使用Hystrix无效原因及解决方法

    最近项目重构使用了Spring Boot和Spring Cloud.这两者结合确实给项目带来了方便,同时也遇到了一些问题.其中使用feign作为服务消费,但是断路器hystrix一直不起作用让人很费解 ...

  3. 在dropwizard中使用feign,使用hystrix

    前言 用惯了spring全家桶之后,试试dropwizard的Hello World也别有一帆风味.为了增强对外访问API的能力,需要引入open feign.这里简单在dropwizard中使用fe ...

  4. spring cloud feign不支持@RequestBody+ RequestMethod.GET,报错

    1.问题梳理: 异常:org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not ...

  5. 【微服务】之五:轻松搞定SpringCloud微服务-调用远程组件Feign

    上一篇文章讲到了负载均衡在Spring Cloud体系中的体现,其实Spring Cloud是提供了多种客户端调用的组件,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使 ...

  6. Spring Cloud Feign 整合 Hystrix

    在前面随笔Spring Cloud 之 Feign的feign工程基础上进行改造 1.pom.xml依赖不变 2.application.yml文件添加feign.hystrix.enabled=tr ...

  7. Spring Cloud 之 Feign

    新建Spring Boot工程,命名为feign 1.pom.xml添加依赖 <?xml version="1.0" encoding="UTF-8"?& ...

  8. SpringCloud Feign对Hystrix(断路由)的支持

    第一步:首先开启Feign对Hystrix的支持,在properties文件中添加以下配置: feign.hystrix.enabled=true. 第二步:在上一篇Feign的基础上添加Hystri ...

  9. SpringCloud Feign使用详解

    添加依赖: <dependency> <groupId>org.springframework.cloud</groupId> <artifactId> ...

  10. 工作随笔——自动重发的凶手--feign

    公司使用的feign(https://github.com/OpenFeign/feign)作为http客户端. 开发时debug后端程序,发现同一个请求会多次收到. 为了判断是谁在搞鬼,在客户端和服 ...

随机推荐

  1. Python基础3--Python复杂数据类型

    1 堆 堆是一种二叉树,其中每个父节点的值都小于或等于其所有子节点的值,最小的元素总是位于二叉树的根节点. 堆的创建 import heapq import random data = range(1 ...

  2. [转载] python必碰到的问题---encode与decode,中文乱码

    阅读来源: 字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicod ...

  3. 搜索入门_简单搜索bfs dfs大杂烩

    dfs题大杂烩 棋盘问题  POJ - 1321 和经典的八皇后问题一样.  给你一个棋盘,只有#区域可以放棋子,同时同一行和同一列只能有一个棋子. 问你放k个棋子有多少种方案. 很明显,这是搜索题. ...

  4. 各种浏览器兼容trim()的方法

    一.利用while方法解决 function trim(str) { while (str[0] == ' ') { str = str.slice(1); } while (str[str.leng ...

  5. set_union和set_intersection

    set_union使用方法: set_union(集合A起始地址,集合A终止地址,集合B起始地址,集合B终止地址,插入C集合中(操作:inserter(C.C.begin()))); set_inte ...

  6. selected标签判断默认选中

    <select name="suggestedType" style="width:280px" > <option value=" ...

  7. 【liunx】sftp常用命令

    sftp是Secure FileTransferProtocol的缩写,安全文件传送协议.可以为传输文件提供一种安全的加密方法.sftp与 ftp有着几乎一样的语法和功能.SFTP为 SSH的一部分, ...

  8. vue 插件tab选项卡(转载)

    <template> <tab :options="tabOpt" :state.sync="stateIndex"></tab& ...

  9. 梯度下降与pytorch

    记得在tensorflow的入门里,介绍梯度下降算法的有效性时使用的例子求一个二次曲线的最小值. 这里使用pytorch复现如下: 1.手动计算导数,按照梯度下降计算 import torch #使用 ...

  10. confluence 新tab 页面打开 kibana short link

    confluence 新tab 页面打开 https://confluence.atlassian.com/confkb/how-to-force-links-to-open-in-a-new-win ...