JavaJava 程序员

程序员如果都懂SpringWebFlux框架的话,也不用天天CR

2022-09-29  本文已影响0人  马小莫QAQ

Spring WebFlux框架

Spring WebFlux是Spring 5发布的响应式Web框架,从SpringBoot 2.x开始,默认采用Netty作为非阻塞I/O的Web服务器。

Spring WebFlux概述

Spring WebFlux基于Reactor框架,同时支持RxJava类库,构建响应式编程框架。查看WebFlux的Maven依赖,可以发现它依赖的项目工程包有Reactor、Spring、ReactiveX、RxJava等模块,使用WebFlux需要单独引用它的依赖包,WebFlux主要的包依赖关系如下图所示。

WebFlux的主要特征

WebFlux的主要模块

WebFlux的应用方式可以使用基于Spring Boot提供的开发模板,直接访问Spring Initializ网站,创建一个Maven或者Gradle项目,需要添加的依赖如下:

在选择Spring Boot版本号时,需要选择2.0.0M2以后的版本才能正确加载WebFlux依赖包,下图是官方提供的Spring WebFlux与SpringMVC的架构对比。

从图中我们可以基本了解WebFlux的主要模块。

WebFlux服务器开发

下面我们分别演示WebFlux提供的注解控制器模式和函数端点模式。介绍使用这两种编程模型实现的代码示例,以及它们不同的语法和差异。

注解控制器模式

注解控制器模式与Spring MVC一致,都基于与Spring Web模块相同的注释。Spring MVC和WebFlux控制器都支持反应式(Reactor、RxJava)数据返回类型,因此不容易将它们分开。一个显著的区别是,WebFlux还支持注解@RequestBody来处理事件响应。

首先,我们实现一个获取用户数据的Service,代码如下。

然后,定义UserController类,它是具体的Spring MVC控制器,

使用UserService获取数据,代码如下。

然后,定义UserController类,它是具体的Spring MVC控制器,使用UserService获取数据,代码如下。

函数端点模式

函数端点模式是基于Lambda的轻量级功能编程模型。可以将其视为小型库或应用程序,是可用于路由和处理请求的一组实用程序。它与注解控制器模式的巨大差异在于,应用程序负责从开始到结束的请求处理,并通过注解声明完成请求回调处理。

下面代码实现了TaskHandler(实现对UserService的访问)。

将URL路由和Handler函数绑定,代码如下。

说明:WebFlux通过配置函数路由(RouterFunction)的方式来实 现 请 求 的 映 射 , 处 理 TaskHandler 的 方 法 的 返 回 类 型 是Mono<ServerResponse>。

下面我们看一下@FunctionInterface查看route的实现源码:

从 源 码 中 , 我 们 发 现 RouterFunction 返 回 一 个 <T extendsServerResponse>对象。在DefaultRouterFunction类中可以看到,在该类的route方法中可以判断请求的参数,如果值为空,则返回Empty,否则返回Mono<HandlerFunction<T>>的一个函数式接口,而这个函数就是Config中配置路由断言时指定的HandlerFunction。源码如下:

总之,由上面的源码分析可知,WebFlux底层虽然和传统的SpringWeb工作机制完全不同,但是WebFlux依然支持基于注解驱动的编程模型,区别在于WebFlux的并发模型和阻塞特性。函数端点模式是WebFlux通过配置函数路由的方式,实现请求到业务处理函数的映射。对于HTTP请求是如何从Web引擎映射到具体的实现方法的,下一节我们会继续介绍WebFlux的逻辑处理架构和HTTP请求的路由映射过程。

Spring WebFlux源码架构解析

与Spring MVC使用DispatcherServlet作为Servlet容器承上启下的重要管理组件类似,在Spring WebFlux框架中,DispatcherHandler有着异曲同工的作用。下面我们根据WebFlux源码讲解它的主要接口和模块的相互关系,WebFlux的工作流程如下图所示。

下面我们按照流程图的执行序列由上至下分别加以解释。

HTTP请求进入WebFlux处理引擎(Server Engine),这里默认使用Netty作为HTTP容器引擎。如果你想修改Servlet容器的服务引擎,则需要在pom.xml文件中添加相应的容器依赖Starter包,这里每个服务引擎都会有自己独立的响应适配器(Adapter)映射HTTP请求或响应到Server HttpRequest和Server HttpResponse组件。

HttpHandler 请 求 处 理 阶 段 主 要 将 输 入 的 请 求 ( ServerHttpRequest、Server HttpResponse,包括用户Session及相关信息)转换为Server WebExchange。

在WebFilterChain阶段,WebFlux会遍历之前注册到Spring容器的WebFilter对象,WebFilterChain负责执行所有WebFilter中的filter方法。对于WebFlux的Filter功能,一种方式是使用WebFilter在Spring MVC中的Filter接口,以接口的形式放回Mono<Void>;另一种方式是使用HandlerFilterFunction接口实现函数式的过滤调用。

如果所有WebFilter都通过执行并放行请求继续执行,那么WebFilterChain将调用WebHandler接口。

DispatcherHandler 实 现 了 WebHandler 接 口 , 同 时DispatcherHandler是WebFlux实现HTTP消息从框架层映射到对应业务逻辑方法的关键实现类。下面是WebFlux处理消息分发的关键源码:

从 上 面 的 代 码 可 知 , DispatcherHandler 的 主 要 流 程 是 遍 历HandlerMapping数据结构,并封装成数据流类Flux。它会触发对应的handler方法,执行相应的业务代码逻辑,而HandlerMapping在配置阶段 会 根 据 @Controller 、 @RequestMapping 、 @GetMapping 、@PostMapping注解注册对应的业务方法到HandlerMapping接口,这也是 WebFlux 兼 容 注 解 方 式 的 原 因 。 这 些 配 置 路 由 最 终 都 会 通 过getHandler()方法找到对应的处理类。

最后是
RequestMappingHandlerAdapter处理阶段,这个映射关系也适用于HandlerAdapter,我们将响应结果转变为数据流返回给handlerResult方法,并将结果转换成数据流序列返回。

WebClient开发

WebClient是从Spring WebFlux 5.0开始提供的一个非阻塞的、基于响应式编程范式的HTTP请求客户端工具。WebClient与传统的RestTemplate的主要区别在于基于函数式、响应式和流式的API,使用声明式的代码风格。同时WebClient依赖非阻塞式的编解码器来完成HTTP的请求和响应。

WebClient的构建

下 面 是 构 建 WebClient 的 一 个 简 单 实 例 。 首 先 通 过WebClient.create方法创建一个WebClient实例,然后通过get、post等方法选择适当的客户端调用方式,uri用来指定需要请求的路径,retrieve用来发起请求并获得响应,bodyToMono(String.class)用来指定请求结果需要处理为String、并包装为Reactor的Mono对象,代码如下所示:

除 了 通 过 create 方 法 构 建 WebClient , 也 可 以 通 过WebClient.builder方法创建WebClient.Builder对象。在对Builder对象进行一些配置后调用build方法创建WebClient对象。通过注册一个客 户 端 Filter ( ExchangeFilterFunction ) 实 现 拦 截 和 修 改HttpClient的头信息,同时执行baseURL和默认Cookie的构建,代码示例如下:

Builder自带的API构建WebClient的方法还有下面几种。

WebClient的响应解析

WebClient使用retrieve()方法作为获取HTTP响应的最简单方法。它可以接受单个对象(

Mono),也可以接受数据流(Flux),同时可以判断返回的响应处理逻辑。

WebClient使用exchange

相比retrieve方法,WebClient使用exchange方法可以对HTTP响应提 供 更 多 控 制 , 获 得 自 己 定 制 的 或 者 想 要 的 结 果 。 可 以 对clientResponse对象执行flatMap操作,代码如下。

WebClient提交Body

假 设 WebClient 需 要 提 交 一 个 JSON 对 象 , 如 {“name” :“hello”,“id”:“123”},需要将这个对象传递给远端服务,WebClient会使用ReactiveAdapterRegitry来处理,将Body的异步编解码过程转换为JSON对象,代码示例如下。

当然也可以将编码后的JSON对象直接传递给WebClient,需要在HTTP头信息中指定ContentType为application/json,也可以加上charset编码。在默认情况下,WebClient将根据请求传递的对象进行解析,处理后自动选择ContentType。代码示例如下。

服务端推送事件

SSE服务端

服务端推送事件(Server-Sent Events,SSE)允许服务端不断地推送数据到客户端。相对于WebSocket而言,服务端推送事件只支持服务端到客户端的单向数据传递。SSE也是WebSocket的一个轻量级的替代方案,虽然功能较弱,但优势在于,SSE在已有的HTTP上可以使用简单易懂的文本格式来表示传输的数据。作为W3C的推荐规范,SSE在浏览器端的支持也比较广泛,除了IE,其他浏览器也都提供了支持。在IE上,也可以使用polyfill库来提供支持。对服务端来说,SSE是一个不断产生新数据的流,非常适合用响应式流来表示。在WebFlux中创建SSE 的 服 务 端 是 非 常 简 单 的 , 只 需 要 返 回 的 对 象 类 型 是Flux<ServerSentEvent>,就会自动按照SSE规范要求的格式来发送响应。

SseController是一个使用SSE的控制器。其中,randomNumbers方法 表 示 每 隔 一 秒 产 生 一 个 随 机 的 SSE 端 点 。 我 们 可 以 使 用ServerSentEvent.Builder类来创建ServerSentEvent对象。这里我们指定了事件的名称random,以及每个事件的标识符和数据。事件的标识符是一个递增的整数,而数据则是产生的随机数。下面的代码演示了服务推送事件。

在测试SSE时,我们只需要使用curl来访问即可。下面的代码给出了调用curl http://localhost:8080/sse/randomNumbers的结果。

SSE客户端

WebClient还可以用同样的方式来访问SSE服务。这里我们访问的是在之前内容中创建的产生随机数的SSE服务。使用WebClient访问SSE服务在发送请求部分与访问Rest API是相同的,区别在于对HTTP响应的 处 理 。 由 于 SSE 服 务 的 响 应 是 一 个 消 息 流 , 我 们 需 要 使 用flatMapMany把Mono<ServerResponse>转换成Flux<ServerSentEvent>对象,这是通过BodyExtractors.toFlux方法完成的。

参数
newParameterizedTypeReference<ServerSentEvent<String>>(){}表明了响应式流中的内容是ServerSentEvent对象。由于SSE服务端会不断地发送消息,这里我们只是通过buffer方法来获取前10条消息并输出,代码如下所示。

Spring WebFlux的优势与局限

在传统的Java后台服务端开发中,我们使用Spring MVC框架的项目比较多,一个很自然的问题就是,对Spring MVC与Spring WebFlux技术栈的选择问题。开发者需要考虑从Spring MVC转型到SpringWebFlux框架的优势与局限。

Spring WebFlux与Spring MVC

下面是官方展示的Spring MVC与Spring WebFlux的框架对比图,可以看出两者在组件功能上的差异。

从上面的图中,我们可以看出两个框架在下面几方面的异同。

Spring WebFlux的适用性

通过上面的特性对比,我们可以发现,虽然Spring WebFlux有诸多性能优势,但是,在业务的适用性和开发者的学习成本上还是有一定限制的。所以我们在架构迁移之前,需要做好准备,才能避免更多问题。下面是Spring官方给出的一些建议。

Spring WebFlux的局限

在使用Spring WebFlux过程中,我们很容易犯一个错误,就是误认为只要使用Spring WebFlux,我们的Web服务框架就能在性能上得到极大的提升。而本质上,性能是由很多不同的指标来度量的。

根 据 Spring 官 方 对 Spring WebFlux 框 架 的 性 能 分 析 , SpringWebFlux并不能使我们的程序跑得更快,在没有WebClient的情况下,请求的延迟时间可能比阻塞式Web框架更长。Spring WebFlux的真正优势是解决Web的吞吐问题,通过非阻塞的编程模型范式可以避免线程的阻塞等待,从而提升系统的整体服务容量。也就是说,SpringWebFlux通过少量的线程就可以处理和应付流量激增的请求,在牺牲小部分请求延迟的情况下,系统的整体资源利用率仍然可以保持稳定,而这要得益于响应式编程模型和非阻塞线程处理模型。

目前在Java企业开发中,Spring WebFlux是相对成熟的非阻塞式Web开发解决方案。虽然目前Spring生态中有对Redis和MongoDB的非阻塞框架支持,但是上述两种存储方案都基于内存的数据库,而SpringWebFlux访问关系数据库就成为一个绕不开的问题。我们知道JDBC连接池采用阻塞方式,如果使用阻塞的数据库访问方式,那么SpringWebFlux就会退化为传统的阻塞调用方式。

虽然目前有类库宣称已经实现了对JDBC的异步调用,但是并没有成熟的案例应用到生产或者实践中。如果你已经有了一个大型研发团队,还要用Spring WebFlux技术栈,就必须要权衡陡峭的学习曲线和实际的项目收益。如果想要在实际项目中应用异步非阻塞框架,一个切实可行的方法就是使用Spring WebFlux技术组件,如WebClient,通过渐进的技术模块逐步了解相关的技术生态。

Spring WebFlux还有一个局限,就是它的学习曲线相对命令式编程语言还是比较高的,响应式编程模型比函数式编程在语法上更难掌握。习惯于面向对象编程思维的开发者不容易适应响应式编程风格和以数据流驱动的思维模式。这给聚焦业务功能的开发者带来了较高的技术门槛。另外,响应式编程中常用的操作符,也比较难掌握,需要花费额外的工夫和精力才能完全掌握它的具体用法。

总结一下,响应式编程是高负载、高并发、大数据量场景下的应用解决方案,适用于在异步边界作为非阻塞模块交互的技术解决方案。如果你的应用对消息的实时性、高负载、用户量等方面没有太大的诉求,那么使用Spring MVC这样传统的编程框架就足够。所以,在进行技术选型或者编程模型选择时,首先要从业务的性质、用户规模和实际使用场景出发,还要考虑团队技术人员的学习能力和知识储备。选择Spring WebFlux作为Web服务器框架还需要从上述技术、业务、人员等因素来权衡利弊。

上一篇 下一篇

猜你喜欢

热点阅读