SpringWebflux详细讲解

1.背景

2.Spring5 框架新功能(Webflux)

2.1.SpringWebflux 介绍

(1)webFlux是 Spring5 添加的新模块,用于 web 的开发,功能和 SpringMVC 类似的,Webflux 使用 当前一种比较流程响应式编程出现的框架。

在spring5的jar包中和架构图中我们都可以看见

(2)使用传统 web 框架,比如 SpringMVC,这些基于 Servlet 容器,Webflux 是一种异步非阻塞的框架,

  异步非阻塞的框架在 Servlet3.X以后才支持,核心是基于 Reactor 的API 实现的。

(3)异步与同步,阻塞与非阻塞的理解

  这里做一个简单的通俗的解释:

  1.异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步

  2.阻塞和非阻塞针对被调用者,被调用者接受到请求之后,做完请求任务之后才给出反馈就是阻塞,接受到请求之后马上给出反馈然后再去做事情就是非阻塞

  如果想深入理解并实践应用异步同步阻塞非阻塞等技术,可能会涉及到网络编程、socket、BIO、NIO、AIO、Netty等技术,大家可以学习之前讲的《网络编程系列课程》

(4)Webflux 特点:

特性一、 异步非阻塞
SpringMVC是同步阻塞的IO模型,资源浪费相对来说比较严重,当我们在处理一个比较耗时的任务时,
例如:上传一个比较大的文件,首先,服务器的线程一直在等待接收文件,在这期间它就像个傻子一样等在那儿(放学别走),什么都干不了,好不容易等到文件来了并且接收完毕,
我们又要将文件写入磁盘,在这写入的过程中,这根线程又要等到文件写完才能去干其它的事情。这一前一后的等待,浪费了大量的资源。
而Spring WebFlux就是来解决这个问题的,
Spring WebFlux可以做到异步非阻塞。还是上面那上传文件的例子,
Spring WebFlux是这样做的:线程发现文件还没准备好,就先去做其它事情,当文件准备好之后,通知这根线程来处理,
当接收完毕写入磁盘的时候(根据具体情况选择是否做异步非阻塞),写入完毕后通知这根线程再来处理(异步非阻塞情况下)。
这个设计相对于SpringMVC而言,可以大大节省系统资源。 特性二、 响应式(reactive)函数编程 Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求,如果你觉得java8的lambda写起来很爽,那么,你会再次喜欢上Spring WebFlux, 因为它支持函数式编程,得益于对于reactive-stream的支持(通过reactor框架来实现的)。 特性三、 不再拘束于Servlet容器 以前,我们的应用都运行于Servlet容器之中,例如我们大家最为熟悉的Tomcat, Jetty...等等。
而现在Spring WebFlux不仅能运行于传统的Servlet容器中(前提是容器要支持Servlet3.1,因为非阻塞IO是使用了Servlet3.1的特性),还能运行在支持NIO的Netty和Undertow中。

(5)Webflux与SpringMVC的区别

  区别一: 两个框架都可以使用注解方式,都可以运行在 Tomet 等容器中
  区别二: SpringMVC 采用命令式编程,Webflux 采用异步响应式编程

  

   具体的可以看后面的案例演示,可能会更好理解!

  (6)总结(面试的时候很重要)

  特点
  1.webflux是一个异步非阻塞的Web框架,它能够充分利用多核CPU的硬件资源去处理大量的并发请求

  2.内部使用的是响应式编程,以Reactor库为基础,基于异步和事件驱动,可以让我们在不扩充硬件资源的前提下,提升系统的吞吐量和伸缩性。

  3.不能使接口的响应时间缩短,它仅仅能够提升吞吐量和伸缩性。

  应用场景
  1.特别适合在IO密集型的服务中,比如微服务网关。

  2.IO 密集型包括:磁盘IO密集型, 网络IO密集型,

  微服务网关就属于网络 IO 密集型,使用异步非阻塞式编程模型,能够显著地提升网关对下游服务转发的吞吐量。

  选WebFlux还是Spring MVC
  1.WebFlux不是 Spring MVC的替代方案!虽然 WebFlux 也可以被运行在 Servlet 容器上(需是 Servlet 3.1+ 以上的容器),

  但是 WebFlux 主要还是应用在异步非阻塞编程模型,而 Spring MVC 是同步阻塞的,

  如果你目前在 Spring MVC 框架中大量使用非同步方案,那么,WebFlux 才是你想要的,否则,使用 Spring MVC 才是你的首选。

  2.在微服务架构中,Spring MVC 和 WebFlux 可以混合使用,比如已经提到的,对于那些 IO 密集型服务(如网关),我们就可以使用 WebFlux 来实现。

2.2.响应式编程(Java 实现)

package com.ldp.testWebflux;


import java.util.Observable;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/15 7:14
 * @description <p>
 * 什么是响应式编程
 * 响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便
 * 地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
 * 电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公
 * 式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
 * </p>
 */
public class Test01Observer extends Observable {
    /**
     * @param args
     */
    public static void main(String[] args) {
        Test01Observer observer = new Test01Observer();
        // 添加观察者
        observer.addObserver((o, arg) -> {
            System.out.println("通知数据变化---1");
        });
        observer.addObserver((o, arg) -> {
            System.out.println("通知数据变化---2");
        });
        observer.addObserver((o, arg) -> {
            System.out.println("通知数据变化---3");
        });
        // 数据变化
        observer.setChanged();
        // 通知
        observer.notifyObservers();
        System.out.println("代码执行完成.....4");
    }
}
View Code

2.3.响应式编程(Reactor 实现)

引入依赖

 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
package com.ldp.reactor;

import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/15 7:40
 * @description * <p>
 * 单词认识
 * 1.mono
 * 英 [ˈmɒnəʊ]   美 [ˈmɑːnoʊ]
 * adj.单声道的   n.单声道录音(或放音)系统
 * 2.flux
 * 英 [flʌks]   美 [flʌks]
 * n.不断的变动;不停的变化;通量;流动  v.熔化;熔解;流出
 * </P>
 * Publisher 发送(生产者,可以类比为把数据放入redis)
 * subscribe 订阅(消费者,可以类比为从redis中读取数据)
 * <p>
 * 1.响应式编程操作中,Reactor 是满足 Reactive 规范框架
 * 2.Reactor 有两个核心类,Mono 和 Flux,这两个类实现接口 Publisher,提供丰富操作API;
 * 2.1.Flux 对象实现发布者,返回 N 个元素;
 * 2.2.Mono 实现发布者,返回 0 或者 1 个元素
 * <p>
 * 3.Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号:
 * 元素值
 * 错误信号
 * 完成信号
 * 错误信号和完成信号:都代表终止信号,终止信号用于告诉订阅者数据流结束了;
 * 错误信号终止数据流同时把错误信息传递给订阅者;
 */
public class Test01Reactor {
    /**
     * 简单使用
     */
    @Test
    public void test01() {
        // just方法直接声明
        // 2.1.Flux 对象实现发布者,返回 N 个元素 (实际开发中我们可以放入产品列表数据)
        Flux.just("张三", "李四", "王五", "赵六").subscribe(System.out::println);
        System.out.println("====================================================");
        // 2.2.Mono 实现发布者,返回 0 或者 1 个元素 (实际开发中我们可以放入根据id查询的单条数据)
        Mono.just("张无忌").subscribe(System.out::println);
    }

    /**
     * Flux 的formXXX声明
     */
    @Test
    public void test02() {
        System.out.println("数组.....");
        Integer[] array = {1, 2, 3, 4};
        Flux.fromArray(array).subscribe(System.out::println);

        System.out.println("集合.....");
        List<Integer> list = Arrays.asList(array);
        Flux.fromIterable(list).subscribe(System.out::println);

        System.out.println("流.....");
        Stream<Integer> stream = list.stream();
        Flux.fromStream(stream).subscribe(System.out::println);
    }
}
View Code

2.4.SpringWebflux 执行流程和核心 API

SpringWebflux 执行过程和 SpringMVC 相似的

1.SpringWebflux 核心控制器 DispatchHandler,实现接口 WebHandler,WebHandler 有一个方法handle

SpringWebflux 里面 DispatcherHandler,负责请求的处理

1. HandlerMapping:请求查询到处理的方法

2. HandlerAdapter:真正负责请求处理

3. HandlerResultHandler:响应结果处理

源码解读

2.5.SpringWebflux(基于注解编程模型)

用法与springmvc几乎是一样的,只是service的实现类有点不一样,这里以Product模型案例讲解

备注
SpringMVC 方式实现,同步阻塞的方式,基于 SpringMVC+Servlet+Tomcat
SpringWebflux 方式实现,异步非阻塞 方式,基于 SpringWebflux+Reactor+Netty

步骤一:编写Product模型

package com.ldp.reactor.model;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/16 6:42
 * @description
 */
public class Product {
    private Integer id;
    private String productNo;
    private String productName;

    public Product() {
    }

    public Product(Integer id, String productNo, String productName) {
        this.id = id;
        this.productNo = productNo;
        this.productName = productName;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getProductNo() {
        return productNo;
    }

    public void setProductNo(String productNo) {
        this.productNo = productNo;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    @Override
    public String toString() {
        return "Product{" +
                "id=" + id +
                ", productNo='" + productNo + '\'' +
                ", productName='" + productName + '\'' +
                '}';
    }
}
View Code

步骤二:编写IProductService接口

package com.ldp.reactor.service;

import com.ldp.reactor.model.Product;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/16 6:58
 * @description
 */
public interface IProductService {
    //根据 id 查询用户
    Mono<Product> getProductById(int id);

    //查询所有用户
    Flux<Product> getAllProduct();

    //添加用户
    Mono<Void> saveProductInfo(Mono<Product> user);

}
View Code

步骤三:编写ProductServiceImpl实现

注意:与springmvc相比较就是这里不一样,因此在能熟练运用springmvc的情况下使用SpringWebflux,应该还是比较容易

package com.ldp.reactor.service.impl;

import com.ldp.reactor.model.Product;
import com.ldp.reactor.service.IProductService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/16 7:01
 * @description
 */
@Service
public class ProductServiceImpl implements IProductService {
    //创建 map 集合存储数据
    public Map<Integer, Product> products = new HashMap<>();

    public ProductServiceImpl() {
        for (int i = 1; i <= 10; i++) {
            this.products.put(i, new Product(i, "P1001" + i, "苹果-" + i));
        }
    }

    @Override
    public Mono<Product> getProductById(int id) {
        return Mono.justOrEmpty(this.products.get(id));
    }

    @Override
    public Flux<Product> getAllProduct() {
        return Flux.fromIterable(this.products.values());
    }

    @Override
    public Mono<Void> saveProductInfo(Mono<Product> ProductMono) {
        return ProductMono.doOnNext(product -> {
            //向 map 集合里面放值
            int id = products.size() + 1;
            products.put(id, product);
        }).thenEmpty(Mono.empty());
    }
}
View Code

步骤四:ProductController的编写

package com.ldp.reactor.controller;

import com.ldp.reactor.model.Product;
import com.ldp.reactor.service.IProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/16 7:11
 * @description
 */
@RestController
public class ProductController {
    @Autowired
    private IProductService productService;

    /**
     * id 查询
     *
     * @param id
     * @return
     */
    @GetMapping("/product/{id}")
    public Mono<Product> getProductId(@PathVariable int id) {
        return productService.getProductById(id);
    }

    /**
     * @return
     */
    @GetMapping("/product")
    public Flux<Product> getProducts() {
        return productService.getAllProduct();
    }

    /**
     * @param Product
     * @return
     */
    @PostMapping("/saveProduct")
    public Mono<Void> saveProduct(@RequestBody Product Product) {
        Mono<Product> ProductMono = Mono.just(Product);
        return productService.saveProductInfo(ProductMono);
    }
}
View Code

步骤五:编写接口测试

package com.ldp.reactor.controller;


import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.alibaba.fastjson.JSON;
import com.ldp.reactor.Base;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/16 7:15
 * @description
 */
public class ProductControllerTest extends Base {

    @Test
    public void getProductId() {
        String url = urlLocal + "/product/11";
        System.out.println("请求地址:" + url);
        HttpRequest request = HttpUtil.createRequest(Method.GET, url);
        request.setConnectionTimeout(60 * 1000);
        String response = request.execute().body();
        System.out.println("响应结果:" + response);
    }

    @Test
    public void getProducts() {
        String url = urlLocal + "/product";
        System.out.println("请求地址:" + url);
        HttpRequest request = HttpUtil.createRequest(Method.GET, url);
        request.setConnectionTimeout(60 * 1000);
        String response = request.execute().body();
        System.out.println("响应结果:" + response);
    }

    @Test
    public void saveProduct() {
        String url = urlLocal + "/saveProduct";
        System.out.println("请求地址:" + url);
        HttpRequest request = HttpUtil.createRequest(Method.POST, url);
        Map<String, Object> map = new HashMap<>();
        map.put("productNo", "P203");
        map.put("productName", "习惯03");
        String jsonStr = JSON.toJSONString(map);
        request.body(jsonStr);
        System.out.println("请求参数:" + jsonStr);
        request.setConnectionTimeout(60 * 1000);
        String response = request.execute().body();
        System.out.println("响应结果:" + response);
    }
}
View Code

2.6.SpringWebflux(基于函数式编程模型)

1.在使用函数式编程模型操作时候,需要自己初始化服务器

2.基于函数式编程模型时候,有两个核心接口:
RouterFunction(实现路由功能,请求转发给对应的 handler)
HandlerFunction(处理请求生成响应的函数)。

3.核心任务定义两个函数式接口的实现并且启动需要的服务器。

4.SpringWebflux 请求和响应不再是ServletRequest和ServletResponse ,而是
ServerRequest 和 ServerResponse

在基于注解实现的情况下具体实现步骤如下

步骤一:编写Handle处理器

package com.ldp.reactor.handler;

import com.ldp.reactor.model.Product;
import com.ldp.reactor.service.IProductService;
import com.ldp.reactor.service.impl.ProductServiceImpl;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import static org.springframework.web.reactive.function.BodyInserters.fromObject;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/16 7:51
 * @description
 */
public class ProductHandler {
    private IProductService productService;

    public ProductHandler() {
        this.productService = new ProductServiceImpl();
    }

    //根据 id 查询
    public Mono<ServerResponse> getProductById(ServerRequest request) {
        //获取 id 值
        int ProductId = Integer.valueOf(request.pathVariable("id"));
        //空值处理
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
        //调用 service 方法得到数据
        Mono<Product> productMono = this.productService.getProductById(ProductId);
        //把 ProductMono 进行转换返回
        //使用 Reactor 操作符 flatMap
        return
                productMono
                        .flatMap(person ->
                                ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                                        .body(fromObject(person)))
                        .switchIfEmpty(notFound);
    }

    //查询所有
    public Mono<ServerResponse> getAllProducts(ServerRequest serverRequest) {
        //调用 service 得到结果
        Flux<Product> products = this.productService.getAllProduct();
        return
                ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(products, Product.class);
    }

    //添加
    public Mono<ServerResponse> saveProduct(ServerRequest request) {
        //得到 Product 对象
        Mono<Product> ProductMono = request.bodyToMono(Product.class);
        return
                ServerResponse.ok().build(this.productService.saveProductInfo(ProductMono));
    }
}
View Code

步骤二:编写启动服务端

package com.ldp.reactor;

import com.ldp.reactor.handler.ProductHandler;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.netty.http.server.HttpServer;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/16 7:58
 * @description
 */
public class FluxServer {
    //1 创建 Router 路由
    public RouterFunction<ServerResponse> routingFunction() {
        //创建 hanler 对象
        ProductHandler handler = new ProductHandler();
        //设置路由
        return RouterFunctions.route(
                GET("/product/{id}").and(accept(APPLICATION_JSON)), handler::getProductById)
                .andRoute(GET("/product").and(accept(APPLICATION_JSON)), handler::getAllProducts)
                .andRoute(POST("/saveProduct").and(accept(APPLICATION_JSON)), handler::saveProduct);
    }

    //2 创建服务器完成适配
    public void createReactorServer() {
        //路由和 handler 适配
        RouterFunction<ServerResponse> route = routingFunction();
        HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
        ReactorHttpHandlerAdapter adapter = new
                ReactorHttpHandlerAdapter(httpHandler);
        //创建服务器
        HttpServer httpServer = HttpServer.create();
        httpServer.handle(adapter).bindNow();
    }

    //最终调用
    public static void main(String[] args) throws Exception {
        FluxServer server = new FluxServer();
        server.createReactorServer();
        System.out.println("enter to exit");
        System.in.read();
    }
}
View Code

步骤三:测试

测试方式一:使用http模拟请求的方式,与springmvc的测试方式一样(略)

测试方式二:使用flux客户端的方式

package com.ldp.reactor;

import com.ldp.reactor.model.Product;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/16 11:03
 * @description
 */
public class FluxClient {
    public static void main(String[] args) {
        //调用服务器地址
        WebClient webClient = WebClient.create("http://localhost:13220");
        //根据 id 查询
        String id = "1";
        Product productResult = webClient.get().uri("/product/{id}", id)
                .accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(Product
                        .class)
                .block();
        System.out.println("查询结果:" + productResult);
        //查询所有
        Flux<Product> results = webClient.get().uri("/product")
                .accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(Product.class);
        results.map(product -> product)
                .buffer().doOnNext(System.out::println).blockFirst();
    }
}
View Code

完美!

posted @ 2021-02-09 10:32  李东平|一线码农  阅读(10622)  评论(0编辑  收藏  举报