前面铺垫了五篇啦,是时候请出主角了。

今天我们就先来一个简单的案例大伙体验一把 WebFlux。

# 1.什么是 WebFlux

首先我们来看看什么是 WebFlux,Spring 官网上有一张经典的对比图:

看着这张图,我们来仔细分析下传统的 SpringMVC 和 WebFlux 之间的区别。

Spring WebFlux 是一个异步非阻塞式 IO 模型,通过少量的容器线程就可以支撑大量的并发访问,所以 Spring WebFlux 可以有效提升系统的吞吐量和伸缩性,特别是在一些 IO 密集型应用中,Spring WebFlux 的优势明显。例如微服务网关 Spring Cloud Gateway 就使用了 WebFlux,这样可以有效提升网管对下游服务的吞吐量。

不过需要注意的是,接口的响应时间并不会因为使用了 WebFlux 而缩短,服务端的处理结果还是得由 worker 线程处理完成之后再返回给前端。

WebFlux 底层使用 Netty 容器,这点也和我们传统的 SpringMVC 不一样,不过默认端口都是 8080。WebFlux 另外也提供了对 Jetty 以及 Undertow 等容器的支持,具体使用方式和之前松哥 Spring Boot 系列中讲的一样,大家直接在 pom.xml 文件中添加相关的依赖即可。

不过需要注意的是,必须是 Servlet3.1+ 容器,如 Tomcat、Jetty,或者是非 Servlet 容器,如 Netty 和 Undertow。

# 2.什么是 Reactor

接下来还有一个概念需要和大家介绍,那就是 Reactor。

Spring Reactor 是 Pivotal 团队基于反应式编程实现的一种方案,这是一种非阻塞,并且由事件驱动的编程方案,它使用函数式编程实现。关于函数式编程,大家可以回顾松哥本系列前面的文章:WebFlux 前置知识(一)

Reactor 是一个用于 JVM 的完全非阻塞的响应式编程框架,具备高效的需求管理,可以很好的处理 “backpressure”,它可以直接与 Java8 的函数式 API 直接集成,例如 CompletableFuture、各种 Stream 等。

Reactor 还提供了异步序列 API Flux(用于 N 个元素)和 Mono(用于 0|1 个元素),并完全遵循和实现了“响应式扩展规范”(Reactive Extensions Specification)。

换句话说,大家可以把 Reactor 理解为 Java8 中的 Stream(参见WebFlux 前置知识(三))+ Java9 中的 Reactive Stream(参见WebFlux 前置知识(四))。

上面说了这么多,大家最重要是要记住 Flux 和 Mono,因为这两个东西我们在后面会反复用到。

  • Mono:实现发布者 Publisher,并返回 0 或 1 个元素。
  • Flux:实现发布者 Publisher,并返回 N 个元素。

记住关键字,他俩都是发布者 Publisher

# 3.创建工程

为了演示方便,松哥这里就直接采用 Spring Boot 工程了,首先我们创建一个 Spring Boot 工程,需要注意的是,以往创建 Spring Boot 时我们都是选择 Spring Web 依赖,但是这次我们选择 Spring Reactive Web 依赖,如下图:

添加上这一个依赖就 OK 了。

这个时候创建好的 Spring Boot 项目,底层容器是 Netty 而不是我们之前广泛使用的 Tomcat 了。

# 3.1 Mono

项目创建成功后,我们可以先来体验一把 Mono 的功能,添加如下 Controller 进行测试:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
    @GetMapping("/hello2")
    public Mono<String> hello2() {
        return Mono.just("hello2");
    }
}

第一个 /hello 接口就是一个普通的 SpringMVC 中的接口,这个在这里也是支持的。后面的 /hello2 接口返回值则是一个 Mono 对象。

接下来启动项目,然后我们就可以愉快的访问 /hello 和 /hello2 接口了。

有人可能会说这么写的意义何在呢?

上面这个例子确实看不出来意义,我们对上面的代码进行一个改进:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        long start = System.currentTimeMillis();
        String helloStr = getHelloStr();
        System.out.println("普通接口耗时:" + (System.currentTimeMillis() - start));
        return helloStr;
    }

    @GetMapping("/hello2")
    public Mono<String> hello2() {
        long start = System.currentTimeMillis();
        Mono<String> hello2 = Mono.fromSupplier(() -> getHelloStr());
        System.out.println("WebFlux 接口耗时:" + (System.currentTimeMillis() - start));
        return hello2;
    }

    private String getHelloStr() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "hello";
    }
}

我们单独抽取出来一个方法 getHelloStr,在这个方法中,我们首先睡眠两秒钟,然后返回一个字符串,最后在接口中调用该方法获取返回的字符串。

需要注意的是,此时的 Mono 是通过 Mono.fromSupplier 方法获取。

接下来启动项目,我们再次访问这两个接口,打印出来的日志信息如下:

可以看到:

  1. 在普通接口中,请求会被阻塞,所以最终打印出来耗时 2001 毫秒。
  2. 在 WebFlux 接口中,请求不会被阻塞,所以服务端的接口耗时为 0。

这下大家看到差异了吧!这比异步 Servlet 方便多了吧!

# 3.2 Flux

Flux 是我们在 WebFlux 中常用的另外一种返回数据格式,我们一起来看下它的一个简单案例:

@GetMapping(value = "/flux",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> flux() {
    Flux<String> flux = Flux.fromArray(new String[]{"javaboy","itboyhub","www.javaboy.org","itboyhub.com"}).map(s -> {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "my->data->" + s;
    });
    return flux;
}

Flux 也是一个 Publisher,它可以由一个数组产生,上面的代码也都是基本的 Stream 操作,松哥就不再赘述了,不熟悉的小伙伴可以查看WebFlux 前置知识(三)

需要注意的是,这里返回的 Content-Type 是 MediaType.TEXT_EVENT_STREAM_VALUE,即 text/event-stream

启动后,在浏览器端访问,我们来看看结果:

text/event-stream

我们日常开发中,返回的 Content-Type 基本都是 application/json 或者 text/html,很少会用到 text/event-stream,这其实也是服务器向浏览器推送消息的一种方案,这种方案和我们所熟知的 WebSocket 有一些差别,这个松哥下次专门撸一篇文章和大家介绍,这里就先不展开了。

# 4.小结

好啦,今天我们就先通过一个简单的案例和大家展示一下 WebFlux 的基本用法,当然这里还涉及到很多细节,松哥后面继续撸文章和大家介绍。