这个基础的web框架包含在Spring Framwork、Spring Web MVC中,是专门为Servlet API和Servlet容器构建的。Spring WebFlux是一个响应式的Web框架,是在Spring5.0版本以后加入的。它是完全非阻塞的,支持反向流反压(反压通常产生是由于短时间内负载高峰导致系统接收数据的速率远高于它处理数据的速率),它可以运行在Netty、Undertow以及Servlet3.1等容器中。
spring-webmvc以及spring-webflux这两个框架,从名称上都已经反映出它们同时共存于Spring Framework的框架中。每一个框块都是可选的。应用程序可以使用他们中的任何一个;或者在某些案例中可以同时使用,例如带有WebClient的Spring MVC控制器。
概述
为什么我们需要创建Spring WebFlux?
让我们先从这个问题开始。
一部分的原因是需要一个可以以很少数量的线程运行的,非阻塞的Web框架,并且扩展少量的硬件资源就可以解决并发问题。Servlet3.1已经提供了非阻塞I/O的API,然而使用它会导致其他部分的Servlet API出现下面的问题:同步(如Filter、Servlet)或者阻塞(getParameter、getPart)。这是一个诱因,创建一个新的通用API作为基础,用于跨一些非阻塞运行时。还有一个更重要的原因,一些服务器(比如Netty)在异步、非阻塞领域已经享有盛名。
另一部分的原因是函数式编程。在Java5中添加的注解创造了函数式编程的机会(比如注解的REST控制器、单元测试);Java中的函数式编程API,在Java8中添加的lambda表达式也是创建了一个机会。在编程模型层面,Java8使Spring WebFlux提供函数式Web端点的同时完成对控制器的注解。
定义响应式
我们在上面直接谈到“非阻塞”和“函数式”,但响应式是什么意思呢?
”响应式”是围绕着响应变化而构建的一种编程模型——网络组件响应I/O事件,UI控制器响应鼠标事件。在这层意义上,非阻塞是响应式,因为代替阻塞,在这种模式下,操作完成或者数据变为可用都会反应到通知。
在Spring团队中常常将响应式与非阻塞反压关联在一起。在同步、命令式代码中、阻塞式调用形成了一种天然的反压形式,它迫使调用者处于等待状态。在非阻塞代码中,控制事件产生的速度上变得很重要,这样一个快速的生成者不会压跨被它调用的目地主机。
响应流是一个很小的规范(在Java9中得到接受),它定义了异步组件与反压之间的交互规则。例如一个数据仓库(扮演发布者)能够生产HTTP服务(扮演订阅者)的数据并且写入到Response中。这样做的目的是让订阅者能够控制发布者以多快或多慢的速度生成数据(因为发布的数据的多少是以订阅者的订阅数量决定的)。
响应式API
响应流在互操作中扮演着很重要的角色。它更着重于函数库与基本组件,却很少用于应用程序的API,因为它太低层了。应用程序需要高层次的、丰富的、功能性的API来组合异步逻辑——类传于java8中的流API不仅仅用于集合对象(collections),这就是响应库扮演的角色。
Spring WebFluxt选择使用Reactor作为自己的响应式函数库。它提供Mono和Flux API类型来处理数据的序列化问题,0..1由Mono处理,0..N由Flux处理,通过一套丰富的操作符来与ReactiveX操作符词汇保持一致。Reactor是一个响应流的函数库,因此它所有的操作都支持非阻塞的反压。Reactor非常关注服务端的Java,它与Spring密切合作开发。
WebFlux需要Reactor作为核心依赖,但是它也可以通过响应流来与其他的响应式函数库进行交互。一般准则,WebFlux API接受一个单纯的Publisher作为输入,在内部将它转换成响应式类型,计算并且返回Flux或者Mono作为输出。所以,你能通过一些publisher作为输入并且在输出上添加一些操作,但是你需要转换输出以便与其他的响应式函数库一起使用。只要切实可行,WebFlux可以透明地适用于RxJava或者其他响应式函数库。
编程模型
Spring-Web模块包括构成Spring WebFlux的响应式基础,包括HTTP抽象、响应流转换(适配成其他服务器支持的类型)、编解码及一个与Servlet API类似的,但是支持非阻塞协议的WebHandler API。
在此基础上,Spring WebFlux提供了两种编程模型以供选择。
1、注解控制器
与spring-web模块的注解基本相同。Spring MVC与WebFlux控制器都支持响应式(Reactor和RxJava)返回类型,而作为结果,不太容易区分它们。WebFlux也支持响应式的@RequestBody参数,也使区分更加困难。
2、功能切入点
基于lambda、轻量的以及函数式编程模型。你可以把它想像成一个很小的库或者工具集,应用程序可能用它跳转或者处理请求。与【注解控制器】最大的区别就是,应用程序从头到尾负责请求处理而不仅仅只是注解和回调。
适用性
用Spring MVC还是用WebFlux?
其中任何一个作为这个问题的答案都会导致很严重的分歧。而实际上它们二者可以互相协作,却扩大了上述问题的答案范围。这二者都被设计成彼此具备连续性和一致性,他们一起使用是有效的,每个方面的反馈也有利于双方的。下面的图片显示出了二者的关联,包括它们公共的部分以及自身特有的部分。

如下的几个场景可以参考:
1、如果你已经有一个Spring MVC的应用程序工作的很好,也不需要做更多的改变。命令式编程是编写、理解、调试代码的最简单的办法。你有更多的函数库可供选择,因为在历史上看,大多数都是阻塞式的。
2、如果你已经使用了一个非阻塞的web栈,Spring WebFlux也提供了和其他栈的同样的执行模式,和同行优势相当;而且还提供了服务器的选择(Netty、Tomcat、Jetty、Undertow及Servlet3.1+容器);编程模型的选择(注解控制器和函数式Web端点);响应式函数库的选择(Reactor、RxJava或者其他)。
3、如果你对可以和Java8 lambda或者Kotlin一起使用的轻量级、函数式的Web框架感兴趣,你可以使用Spring WebFlux的函数式Web端点。两个框架都支持相同的基于注解式的编程模型,使其更容易重用知识。
4、评估一个应用程序是否简单的办法是检查它的依赖关系。如果使用了阻塞的持久化(JPA、JDBC)或者网络请求的API,Spring MVC至少是通用框架最佳选择。在Reactor和RxJava在单独的线程上执行同步调用在技术上可行的,但是你却无法更加充分的使用非阻塞的web栈。
5、如果你有一个调用远程服务的Spring MVC的应用程序,可以尝试使用响应式WebClient。你能直接通过Spring MVC控制器方法以返回响应类型(Reactor、RxJava或者其他)。每个调用的延迟越大或者众多调用之间的依赖关系越大,好处就越大。Spring MVC控制器也能调用其他的响应组件。
6、如果你有一个庞大的团队,请记住在转换非阻塞式的、函数式以及声明式的编程方式,学习曲线是陡峭的。最实际的办法就是开始不完全使用响应式WebClient。另外小范围的使用并且度量这样做的好处。大多数的情况下,大面积的将应用程序切换到响应式的这种转变是不必要的。如果你无法确定使用响应式有什么好处,那么就从非阻塞I/O是如何工作及它的影响开始学习吧。
服务器
Spring WebFlux支持Tomcat、Jetty、Servlet3.1容器,同样适用于非Servlet运行时的服务器,如Netty及Undertow。所有的服务器都适配了底层的Common API,所以这些服务器都支持高层次的编程模型(响应式模型)。
Spring WebFlux没有内嵌的支持开始或关闭服务的功能。然而通过Spring配置和WebFlux基础组件就可以很容易的构建出一个应用程序,并且通过很短的代码就能运行它。
Spring Boot有WebFlux的Starter可以自动化的完成这些步骤。默认这个Starter使用Netty,但是通过使用Maven或Gradle改变依赖,也可以很容易的将其切换成Tomcat、Jetty或者Undertow。Spring Boot默认使用Netty,因为Netty更广泛的用于异步、非阻塞领域,并且可以让客户端与服务器端共享资源。
Tomcat和Jetty能够同时使用Spring MVC和WebFlux。然而请记住,他们(MVC与WebFlux)的使用方法却有很大的不同。SpringMVC依赖于Servlet阻塞I/O,并且可以让应用程序在必要的情况下直接使用Servlet API。Spring WebFlux依赖于Servlet 3.1非阻塞I/O,并在底层适配器的后面使用Servlet API,没有暴露直接使用它的方式。
对于Undertow,Spring WebFlux直接使用Undertow的API而没有使用Servlet API。
性能
响应式和非阻塞通常不能使应用程序运行的更快。但在一些情况下(如果使用WebClient并行的运行远程调用)它们可以做到这一点。基于上,使用非阻塞的方式法会增加一些工作量,这会稍微的增加请求的处理时长。
使用响应式和非阻塞式的最大好处是有能力缩小服务器的规模,固定的线程数量以及更少的内存。它会使应用程序在负载下更有弹性,因为它能够以更可预期的方式扩展。然而为了能够观察这些优点,你需要做一些延时(混合慢的或者不可能预期的网络I/O)。这才是响应栈能够显示其优势与与不同的地方。
并发模型
Spring MVC和Spring WebFlux都支持注解式控制器,但是他们有一个关键的不同点在于并发模型,对于阻塞和线程的假想。
在Srping MVC(以及普通的Servlet应用程序)中,它假设应用程序能够阻塞当前线程(例如远程调用)。因此Servlet容器使用一个大线程池来吸收请求处理过程中引发的潜在阻塞。
在Spring WebFlux(以及一般的非阻塞服务器)中,它假设应用程序不是阻塞的。因此,阻塞服务器使用一个小的,固定数量的线程池(事件循环处理者)来处理请求。
调用阻塞API
如果你需要使用阻塞函数库。Reactor和RxJava都提供了publishOn运算符,以便在不同的线程中继续处理。这意味着有一个简单的退出非阻塞的方式。然后记住,阻塞API不适用于并发模型。
可变状态
在Reactor和RxJava中,是通过操作符定义逻辑的。有运行时,数据在不同的阶段被持续处理的过程中会形成一个响应式管道。这样做的最大好处就是,它把应用程序从必须保护运行状态中解放了出来,因为应用程序代码从来不会在管道中被并发调用。
线程模型
一个普通的Spring WebFlux(例如没有数据访问也没有其他位的依赖)。我们要求一个线程用于服务器,其他几个线程用于处理请求(和CPU的核心数保持一致)。然而Servlet容器可能会使用更多的线程(例如Tomcat会使用10个)用来同时支持Servlet(阻塞)I/O和Servlet3.1(非阻塞)I/O的使用。
响应式WebClient是以事件循环的方式操作的。所以我们能够它只涉及到看到一个很小的,固定数量的处理线程(例如,使用响应式Netty连接的Reactor-http-Nio)。然而,如果Reactor Netty用于客户端和服务器,那么二者默认共享事件循环资源。
Reactor和RxJava提供线程池上虚拟化,调用调度器,使用publicOn操作符,用于切换到不同的线程池。调度器是按照支持的特定的并发策略的方式命名的,如“parallel”(与CPU绑定的方式工作,限制数量的线程),或者“elastic”(与I/O绑定的方式工作,用一个大数量的线程数)
数据访问的库和第三方的依赖也可以创建和使用它们自己的线程。
响应式的核心
Spring-web模块对于响应式Web应用程序的支持包括如下的功能:
1、关于服务请求的处理有两个层面的支持
HttpHandler:
支持非阻塞I/O和响应流反压的Http处理请求的基本连接,同时支持Reactor Netty、Undertow、Tomcat、Jentty和Servlet3.1+容器的适配器。
WebHandler API:
稍稍高一点的层次,通用的处理请求的Web API,在具体的编程模型(类似于注解控制器和功能切入点)之上构建的。
2、对于客户端层面
存在一个基础的ClientHttpConnector协议用来执行支持非阻塞I/O和响应流反压的Http请求,同时拥有Reactor Netty、响应式jetty HttpClient和Apache HttpComponents的适配器。应用程序中高级的WebClient是构建于这些基础协议之上的。