vlambda博客
学习文章列表

没听过WebFlux?那你肯定不知道Spring-WebFlux

spring-web 就是传统的Spring MVC同步阻塞的web框架
spring-webflux 则是Spring Framework 5.0中引入的新的响应式web框架

接口请求

定义接口与mvc一样

@Operation(summary = "获取产品列表", description = "获取产品列表接口",responses = {@ApiResponse(responseCode = "400", description = "400错误")})
@GetMapping("/productList")
public Flux<ResponseVo<ProductListVo>> getXxxxList() {
    return xxxService.getxxxList().map(ResponseVo::ok);
}

与mvc不同的地方是webflux中返回参数时Flux或者Mono,我的理解是返回的数据由具体的对象变成了数据流,这个时候就需要客户端去订阅消费。Flux和Mono的区就是

  1. flux代表数据流中有0到n个元素

  2. mono代表数据流中有0到1个元素

例子1

假设我们现在有一个需求,就是要按类别查询所有当前可用的产品列表,数据结构对应数据库为:

product_info 商品基本信息表 里面包含所有商品的基本信息
id,name,used,type_id
product_detail_info  商品详细信息表 包含各个商品的详细信息,包括描述、展示图等
product_id,desc,images
product_type 商品类型表
type,type_name

如果使用传统的mvc那么对应的逻辑代码就是先查询产品信息表,再用id去查询详情或者一个连表查询结果就出来了。然后将组装好的数据直接返回给前端即可

public List<ProductInfoVo> getProductList(){
    List<ProductInfoVo> list = Lists.newArrayList();
    List<ProductInfo> productList = productInfoRepository.findAllByUsed(1);
    //按产品类别分组
    Map<Integer,List<ProductInfo>> map = productList.stream().Collect(Collectors.groupingBy(ProductInfo::getTypeId));
    map.keySet().stream().forEach(key -> {
        //查询分组信息
         ProductType typeInfo = typeRepository.findById(key); 
         List<ProductInfo> list = map.get(key);
         //将产品信息与产品详情信息合并
         List<ProductInfoDetailVo> detailList = list.stream().map(info->convertToDetail(info)).Collect(Collectors.toList());
         //添加到最终返回的list中
         list.add(ProductInfoVo.builder()
         type(typeInfo.getTypeName())
         .dataList(detailList)
         .build());
    });
    return list;
}

那么如果使用webflux如何去完成呢,因为我们知道webflux中所有操作都是异步的包括数据库的查询

//这里跟上面的区别就是不是直接返回一个列表而是异步返回一个流
 Flux<ProductInfo> products = productInfoRepository.findAllByUsed(1);
 //按类别分组
 products.groupBy(ProductInfo::getTypeId)
 //转换合并信息
 .flatMap(item->{
     Mono<ProductType> typeMono = typeRepository.findById(item.key());
     //将Type与上面的productInfo列表合并
     return typeMono.zipWith(item.collectList(), (t, f) -> ProductInfoVo.builder()
         type(typeInfo.getTypeName())
         //将产品基本信息转换为我们想要的详细信息并合并,其实就是用产品id去查详情表在将结果信息合并至新的list中
         .dataList(f.stream().map(detail->convertToDetail(detail))..Collect(Collectors.toList());
         .build());
  //将最终结果按id升序输出
 }).sort(Comparator.comparing(ProductInfo::getId));

上面最终返回的就是一个Flux流相当于mvc中的列表。与mvc不同的地方是,我们在编程中不能向之前用上一步的返回结果去进行下一步的获取,而是需要在整个过程中去聚合变换最终得到想要的结果。zipWith是合并两个流的方法。

例子2

还有一种情况是很常见的,比如说我在写业务逻辑的时候需要根据上一个方法的返回值来判断后续的代码执行逻辑。在mvc中就很简单,直接判断返回结果即可

if(result == 1){
    //xxxx具体逻辑
}else{
    //具体逻辑
}

但是在响应式编程中,你不能直接从上一个方法中获取返回结果,当然也有办法就是用Mono或flux的block()方法,但这个方法是阻塞的,这样做就违背了整个响应式编程的初衷。

Mono<ProductInfo> products = productInfoRepository.findByProductId(1);
//如果要对products进行非空判断
products.flatMap(product -> Mono.just(Optional.of(product)))
.defaultIfEmpty(Optional.empty())
           .flatMap(detailOptional -> {
               if (!detailOptional.isPresent()) {
                   return Mono.error(new ServerException(ResponseCode.PRODUCT_ID_ERROR));
               }else{
                   xxxx正常逻辑
               }
           });

只需要对每个对象用optional进行包装再在使用前进行一下判断就好啦。

例子3

使用缓存也一样,redis也支持响应式,跟使用方法跟mvc一样,只是返回结果有了变化跟mysql一样

private final ReactiveStringRedisTemplate redisTemplate;

Mono<String> stringMono = redisTemplate.opsForValue().get(RedisKey.Product_KEY + id);
Mono<String> ticketFromCache = getTicketFromCache();
stringMono.flatMap(ticket -> Mono.just(Optional.of(ticket)))
          .defaultIfEmpty(Optional.empty())
          .flatMap(opt -> opt.<Mono<? extends String>>map(Mono::just)
          .orElseGet(() ->ticketFromCache.flatMap(ticket -> getUserInfo(ticket, account, ids))
          .map(JSONObject::toJSONString))
          .map(json -> JSONObject.parseObject(json, ProductInfoVo.class)));

上面代码的逻辑就是先从缓存中获取产品的信息,如果缓存中没有,就去查询,查询需要ticket,ticket也遵循相同的逻辑先从缓存中获取,如果没有则重新用请求获取,获取之后缓存到redis中。