Feign的使用

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

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

原文地址:https://www.cnblogs.com/linlf03/p/10224236.html