Spring WebFlux简介

发布时间 2023-04-30 16:59:10作者: shigp1

一、为什么需要web Flux

部分原因是需要一个无阻塞的web堆栈来处理少量线程的并发性,并用更少的硬件资源进行扩展。Servlet 3.1确实为非阻塞I/O提供了一个API。然而,使用它会偏离Servlet API的其余部分,在那里交互是同步的(Filter,Servlet)或阻塞的(getParameter,getPart)。这就是一个新的通用API作为任何非阻塞运行时的基础的动机。这一点很重要,因为服务器(如Netty)在异步、非阻塞空间中已经建立起来。
 
答案的另一部分是函数编程。正如在Java 5中添加注释创造了机会(如带注释的REST控制器或单元测试)一样,在Java 8中添加lambda表达式也为Java中的函数API创造了机会。这对于允许异步逻辑的声明性组合的非阻塞应用程序和延续式API(CompletableFuture和ReactiveX推广了这一点)来说是一个福音。在编程模型级别,Java8使SpringWebFlux能够在注释控制器的同时提供功能性的web端点。

二、怎么定义Reactive

术语“反应式”指的是围绕对变化做出反应而建立的编程模型 — 对I/O事件做出反应的网络组件、对鼠标事件做出反应,以及其他。从这个意义上说,非阻塞是被动的,因为我们现在不是被阻塞,而是在操作完成或数据可用时对通知做出反应。命令式编程主要采用顺序,分支和循环三种主要结构运行程序。比如运行:

int a = 10;
int b = 20;
int c = a + b;
System.out.println(c);

运行时输出30,当改变a为20时c还是30。在 Excel 里,C 单元格上设置函数 Sum(A+B),当你改变单元格 A 或者单元格 B 的数值时,单元格 C 的值同时也会发生变化。这种行为就是 Reactive。在 Java 9 Flow 中,按相同的思路实现上述处理流程,当初始变量的值变化,最后结果的值也同步发生变化,这就是响应式编程。这相当于声明了一个公式,输出值会随着输入值而同步变化。

  SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();
  publisher.subscribe(new Flow.Subscriber<Integer>() {
      private Integer sum = 0;
      Flow.Subscription subscription = null;

      @Override
      public void onSubscribe(Flow.Subscription subscription) {

          this.subscription = subscription;
          subscription.request(1);
      }

      @Override
      public void onNext(Integer item) {
          subscription.request(1);
          sum += item;

      }

      @Override
      public void onError(Throwable throwable) {

      }

      @Override
      public void onComplete() {
          System.out.println(sum);
      }

  });

  Arrays.asList(3, 4).stream().forEach(publisher::submit);
  publisher.close();

 
还有另一个重要的机制,Spring团队将其与“反应性”联系在一起,那就是非阻塞性背压。在同步命令式代码中,阻塞调用是一种自然形式的背压,迫使调用方等待。在非阻塞代码中,控制事件的速率变得很重要,这样快速生产者就不会淹没其目的地。在命令式编程中,当消费者速率赶不上生产者速率时,消费者要么将多出来的元素缓存,要么丢弃。在JAVA 8 Stream中,当执行到终端操作时才会拉取元素,因此Stream只能执行一次。但是Reactive是消费者主动拉取生产者的元素,消费者的消费速率是多少就可以控制从生产者拉取多少。这就是非阻塞性背压。
 
Reactive Streams是一个小规范(也在Java 9中采用),它定义了具有背压的异步组件之间的交互。例如,数据存储库(充当发布服务器)可以生成HTTP服务器(充当订阅服务器)可以写入响应的数据。Reactive Streams的主要目的是让订阅者控制发布者生成数据的速度或速度。在 Java 中,有 4 个 Reactive Streams API,在 JUC 的 Flow 类中可以看到:
 

  • Publisher 即事件的发生源,它只有一个 subscribe 方法。其中的 Subscriber 就是订阅消息的对象。
  • Subscriber 作为订阅者,有四个方法。onSubscribe 会在每次接收消息时调用,得到的数据都会经过 onNext 方法。onError 方法会在出现问题时调用,Throwable 即是出现的错误消息。在结束时调用 onComplete 方法。
  • Subscription 接口用来描述每个订阅的消息。request 方法用来向上游索要指定个数的消息,cancel 方法用于取消上游的数据推送,不再接受消息。
  • Processor 接口继承了 Subscriber 和 Publisher,它既是消息的发生者也是消息的订阅者。这是发生者和订阅者间的过渡桥梁,负责一些中间转换的处理

三、编程模型

spring-web模块包含spring WebFlux基础的反应式基础,包括HTTP抽象、支持的服务器的反应式流适配器、编解码器,以及与Servlet API类似但具有非阻塞契约的核心WebHandler API。

在此基础上,SpringWebFlux提供了两种编程模型的选择:

  • 带注解的控制器:与Spring MVC一致,并基于来自Spring web模块的相同注释。Spring MVC和WebFlux控制器都支持反应式(Reactor和RxJava)返回类型,因此,很难将它们区分开来。一个显著的区别是WebFlux还支持反应式@RequestBody参数。
  • 函数端点:基于Lambda的轻量级功能编程模型。您可以将其视为应用程序可以用来路由和处理请求的一个小型库或一组实用程序。与带注释的控制器的最大区别在于,应用程序负责从开始到结束的请求处理,而不是通过注释声明意图并被调用。

四、如何选择

选择Spring MVC还是选择Spring WebFlux?下图显示了两者之间的关系,它们有什么共同点,以及各自唯一支持什么:

 
建议如下:

  • 如果有一个运行良好的SpringMVC应用程序,那么就没有必要进行更改。命令式编程是编写、理解和调试代码的最简单方法。可以最大限度地选择库,因为从历史上看,大多数库都是阻塞的。
  • 如果想选择非阻塞web堆栈,那么Spring WebFlux提供了与该领域其他执行模型相同的执行模型优势,还提供了服务器选择(Netty、Tomcat、Jetty、Undertow和Servlet 3.1+容器)、编程模型选择(带注解的控制器和功能性web端点)以及反应库选择(Reactor、RxJava或其他)。
  • 如果对用于Java 8 lambdas或Kotlin的轻量级、功能性web框架感兴趣,可以使用Spring WebFlux功能性web端点。对于要求不那么复杂的小型应用程序或微服务来说,这也是一个不错的选择,它们可以从更大的透明度和控制中受益。
  • 在微服务架构中,可以将应用程序与Spring MVC或Spring WebFlux控制器或Spring WebFlux功能端点混合使用。在两个框架中都支持相同的基于注释的编程模型,可以更容易地重用知识,同时为正确的工作选择正确的工具。
  • 评估应用程序的一种简单方法是检查其依赖关系。如果有阻塞持久性API(JPA、JDBC)或网络API可供使用,那么Spring MVC至少是通用体系结构的最佳选择。Reactor和RxJava在单独的线程上执行阻塞调用在技术上是可行的,但不会充分利用非阻塞的web堆栈。
  • 如果有一个可以调用远程服务的SpringMVC应用程序,请尝试响应式WebClient。可以直接从Spring MVC控制器方法返回反应类型(Reactor、RxJava或其他)。每次调用的延迟或调用之间的相互依赖性越大,好处就越显著。Spring MVC控制器也可以调用其他响应组件。

 
 
 

参考自:
一文弄懂 Spring WebFlux 的来龙去脉