Spring Boot之WebFlux开发应用
之前给介绍了Spring Boot相关知识,包括定义、特性,相比Spring MVC做的简化配置,以及与redis,dubbo和elasticsearch等组件集成演示,本次主要介绍基于刚发布的Spring Boot 2.0进行WebFlux开发应用,内容包括相关知识介绍和集成案例演示。
1. 背景知识
1.1 Spring Boot2.0
北京时间3月1日,Spring Boot 2.0正式发布Release版本。作为Spring生态中重要的开源项目,Spring Boot旨在帮助开发者更容易的创建基于Spring的应用程序和服务。经历了4年的发展,Spring Boot已经拥有了22000多star,16000次Commits,贡献者超过400多名的超热门开源项目。
其中刚发布的2.0版本是自2014年4月1日发布的1.0版本以来第一次重大修订,也是首个提供对Spring Framework 5.0支持的GA稳定版本,2.0带来了很多新的特性:
- 基于Java 8(最低标准),支持Java 9;
- 基于Spring 5构建的Spring Boot 2.0,通过使用Spring WebFlux提供了响应式Web编程支持;
- 支持HTTP/2;
Spring Boot的Web容器选择中Tomcat、Undertow和Jetty均已支持HTTP/2。HTTP/2较HTTP/1.1在性能上有显著提升,页面加载时间降低了50%,详见 Java 9和Spring Boot2.0纷纷宣布支持的HTTP/2到底是什么。
其余新特性不在此赘述,详见 Spring Boot 2.0 Release Notes。
1.2 Spring WebFlux
Spring WebFlux是一个非阻塞的函数式Reactive Web框架,可以用来构建异步的、非阻塞的、事件驱动的服务,在伸缩性方面表现非常好。名称中的Flux来源于Reactor中的类Flux。
众所周知Spring MVC是同步阻塞的IO模型,资源浪费相对比较严重,当我们在处理一个耗时的任务时,例如上传一个较大的文件时,服务器的线程一直在等待接收文件,这期间什么也做不了,等到文件接收完毕可能又要写入磁盘,写入的过程线程又只能在那等待,非常浪费资源。而Spring WebFlux是这样做的,线程发现文件还没接收好,先去做其他事情,当文件接收完毕后通知该线程来处理,后续写入磁盘完毕后再通知该线程来处理,通过异步非阻塞机制节省了系统资源,极大的提高了系统的并发量。因此对于微服务下的IO密集型的Service来说,WebFlux是一个不错的选择。
Spring WebFlux与Spring MVC框架对比(一)
左边是传统基于Servlet的Spring Web MVC框架,右边是Spring Framework 5.0引入的基于Reactive Streams的Spring WebFlux框架,从上到下依次是Router Functions、WebFlux和Reactive Streams三个新的组件。
- Router Functions:对应@Controller、@RequestMapping等标准的Spring MVC注解,提供了一套函数式编程的API,用于创建Router、Handler和Filter。
- WebFlux:核心组件,用于协调上下游各个组件提供响应式编程的支持。
- Reactive Streams:一种支持Backpressure的异步数据流处理标准,主流实现有RxJava和Reactor,Spring WebFlux默认集成的是Reactor。Backpressure是一种反馈机制,当数据的发布速度超过处理速度时,消费者需要决定缓存还是丢弃,在响应式编程中,决定权交回给了发布者,消费者只需根据自身处理能力向发布者请求相应数量的数据。
- Web容器:Spring WebFlux既支持Tomcat、Jetty等传统容器(前提是支持Servlet3.1+),又支持Netty、Undertow等异步容器。
只能运行在Servlet 3.1+容器上,是因为3.1规范支持异步处理,该功能主要针对业务处理较耗时的情况,可以减少服务器资源占用,提高并发处理速度。
2. Spring WebFlux实战
2.1 WebFlux工程创建
下面我们基于Spring Boot 2.0创建一个WebFlux工程,操作步骤非常简单:
1)点击Create New Project,创建一个新的项目;
2)选择Spring Initializr,并配置JDK版本为1.8,Initializr Service UR按默认配置为https://start.spring.io;
3)在metadata页配置工程包名等信息,其中type项目构建方式选择默认值,使用maven进行项目构建;
4)在Dependencies页,我们先将Spring Boot版本设置为2.0.0,可以看到下方有很多选项可以选择,每个选项代表一个组件,这里选择"Web"->"Reactive Web"组件。可以看到下方提示,Reactive Web development with Netty and Spring WebFlux,即通过Reactive Web构建一个WebFlux应用服务;
5)最后配置下工程名称和项目路径,即完成了Spring WebFlux应用的创建。
默认的demo工程pom中只包括webflux和reactor等组件依赖。
2.2 Hello World应用开发
下面通过编写Handler和Router Functions来实现hello world程序。
1)编写Hello world handler,该类相当于Spring Web中的Service bean;
2)将Hello world handler注册到路由上,类似于Spring Web中的Controller类的创建。除了新的Router Functions接口,Spring WebFlux同时支持使用老的Spring MVC注解声明Reactive Controller。
3)接着运行DemoApplication的main方法,即完成了服务启动,这里默认采用了Netty作为reactor的底层容器启动。
最后访问http://127.0.0.1:8080/hello,返回hello world即表示服务启动和访问成功。
2.3 注册登录应用开发
需要注意目前支持reactive编程的数据库只有MongoDB,Redis,Cassandra,Couchbase,而JDBC与JPA的事务是基于阻塞IO模型的,并不是自然支持reactive编程风格,需要等待Spring Data Reactive升级IO模型才能支持相关数据库事务的使用。
Spring WebFlux与Spring MVC框架对比(二)
这里以Redis作为数据库,实现一个简单的用户注册登录功能。
1)首先配置Spring Data Reactive Redis,默认指向本地6379端口的Redis;
2)编写用户注册登录handler,主要通过RedisConnection进行数据入库和查询操作,并将业务处理结果以Json格式进行返回;
3)添加注册登录路由,将url路由给具体的handler来进行处理;
下面我们来验证下程序的运行情况:
1)首先运行服务,然后通过postman发送用户注册请求;
2)发送注册请求后,除了前台返回{"message":"successful"},同时可以查看Redis中保存的注册信息;
3)接着用刚注册的信息发起登录请求,可以看出返回结果为登录成功。
至此即完成了一个简单的基于Spring WebFlux和Redis的用户注册登录功能开发。
3. 分析总结
通过以上介绍,可以看出基于Spring Boot进行WebFlux开发即简单又高效。下面对WebFlux中几个关键语法点进行介绍:
首先简单说下Reactor的两个关键概念,Mono和Flux是Reactor中的流数据类型,Mono是一个用来发送0或者单值数据的发布器,Flux可以用来发送0到 N 个值。它们表示在订阅这些发布服务时发送的数值流。
如下图中getUserById()返回一个Mono<User>表示其在数据可用的情况下发送0个或者单个用户,getUsers()返回一个用户列表的Flux实例,表示其发送0到多个用户数据。
上文提到的handler处理类相当于服务bean,一般用来编写业务功能,其中返回的ServerResponse类似Spring Web中的ResponseEntity用来封装响应数据,包括状态码、HTTP头等信息,它包含了ok(),notFound()等方法,用来创建不同类型的响应信息。如上图的UserRepository.getUserById()返回一个Mono<User>,而ServerResponse.ok().body(Mono.just(user), User.class) 将这个Mono<User>转成Mono<ServerResponse>,这代表在ServerResponse可用时候发送响应的流。
ServerResponse.notFound().build()返回一个 Mono<ServerResponse>对象,当给定的pathVariable中找不到对应用户信息时返回404的服务器响应信息。
Spring WebFlux除了对响应式http的支持外,还包括服务端推送事件(Server Sent Events,SSE)、WebSocket客户端和服务端的支持。其中服务端推送事件允许服务器不断地推送数据到客户端,它的实现非常简单,只需要返回对象类型配置成Flux<ServerSentEvent>,就会自动按照SSE规范要求的格式发送响应。
在命令式的编程风格中,线程的执行会被堵塞,直到接收到数据。这使得数据在实际返回之前线程必须进行等待。而在Reactive编程中,我们定义了一个流,用来发送数据以及数据返回时所执行的操作。使用这种方法线程不会被堵塞的。当数据返回时框架会选择一个可用的线程进行下一步处理,这就体现出异步非阻塞模式的优势所在。因此响应式编程能带来更快处理速度,更高硬件利用率的未来选择。
参考资料
- https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-fn-handler-functions
- https://coyee.com/article/12086-spring-5-reactive-web
- https://zhuanlan.zhihu.com/p/30813274
- https://github.com/hantsy/spring-reactive-sample#spring-data-redis
- https://www.ibm.com/developerworks/cn/java/spring5-webflux-reactive/index.html