文章目录
一、 WebFlux介绍
1、什么是 WebFlux
Spring Framework 中包含的原始 Web 框架 Spring Web MVC 是专门为 Servlet API 和 Servlet 容器构建的。反应式堆栈 Web 框架 Spring WebFlux 是在 5.0 版的后期添加的。它是完全非阻塞的,支持反应式流(Reactive Stream)背压,并在Netty、Undertow和Servlet 3.1 +容器等服务器上运行。
Spring WebFlux 是一个异步非阻塞式 IO 模型,通过少量的容器线程就可以支撑大量的并发访问。底层使用的是 Netty 容器,这点也和传统的 SpringMVC 不一样,SpringMVC 是基于 Servlet 的。但是接口的响应时间并不会因为使用了 WebFlux 而缩短,服务端的处理结果还是得由 worker 线程处理完成之后再返回给前端。
-
【spring-webmvc + Servlet + Tomcat】命令式的、同步阻塞的
-
【spring-webflux + Reactor + Netty】响应式的、异步非阻塞的
同时要学习 WebFlux
时我发现又需要 Java 8 中的函数式编程、Stream 流等技术作为前置知识,对于lambda和stream流,可以参考:https://blog.csdn.net/lemon_TT/article/details/119415154
2、反应式库介绍
2.1 Reactive Stream
反应式编程(Reactive Programming) ,这是微软为了应对高并发环境下的服务端编程,提出的一个实现异步编程的方案。
反应式流(Reactive Stream) 就是反应式编程相关的规范,在 Java 平台上,由Netflix(开发了 RxJava)、TypeSafe(开发了 Scala、Akka)、Pivatol(开发了 Spring、Reactor)共同制定。
它由以下几个组件组成:
-
发布者:发布元素到订阅者
-
订阅者:消费元素
-
订阅:在发布者中,订阅被创建时,将与订阅者共享
-
处理器:发布者与订阅者之间处理数据
比如Java 平台直到 JDK 9才提供了对于Reactive的完整支持,JDK9也定义了上述提到的四个接口,在java.util.concurrent
包上
// 发布者(生产者)
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
// 订阅者(消费者)
public interface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}
// 用于发布者与订阅者之间的通信(实现背压:订阅者能够告诉生产者需要多少数据)
public interface Subscription {
public void request(long n);
public void cancel();
}
// 用于处理发布者 发布消息后,对消息进行处理,再交由消费者消费
public interface Processor<T,R> extends Subscriber<T>, Publisher<R> {
}
2.2 Reactor
Reactive Stream 是一套反应式编程的规范,但作为应用程序 API,应用程序肯定还是需要一个更高级、更丰富的功能 API 来编写异步逻辑。这就是反应式库所扮演的角色。
Reactor 框架是 Pivotal 基于 Reactive Programming
思想实现的。它符合 Reactive Streams
规范。它提供了Mono
和Flux API
类型,通过一组与 ReactiveX 运算符词汇表一致的丰富运算符来处理 0…1 () 和 0…N ()的数据序列。是一个用于 JVM 的完全非阻塞的响应式编程框架,具备高效的需求管理,可以很好的处理 “backpressure”。**Reactor **就是 Spring WebFlux 的首选 反应式库。
Flux 和 Mono 这两个 Reactor 的核心类,两个都是发布者 Publisher。
-
Mono:实现发布者 Publisher,并返回 0 或 1 个元素。
-
Flux:实现发布者 Publisher,并返回 N 个元素。
2.3 Reactive Stream、Reactor 和 WebFlux 关系
-
Reactive Stream 是一套反应式编程标准和规范;
-
Reactor 是基于 Reactive Streams 一套反应式编程框架;
-
WebFlux 以 Reactor 为基础,实现 Web 领域的 反应式编程框架。
二、入门WebFlux
1、简单介绍
经过上面的基础,我们现在已经能够得出一些结论的了:
-
WebFlux是Spring推出响应式编程的一部分(web端)
-
响应式编程是异步非阻塞的(是一种基于数据流(data stream)和变化传递(propagation of change)的声明式(declarative)的编程范式)
Spring官方为了让我们更加快速/平滑到WebFlux上,之前SpringMVC那套都是支持的。也就是说:我们可以像使用SpringMVC一样使用着WebFlux。WebFlux使用的响应式流并不是用JDK9平台的,而是一个叫做Reactor响应式流库。Reactor是一个响应式流,它也有对应的发布者(Publisher
),Reactor的发布者用两个类来表示:
-
Mono(返回0或1个元素)
-
Flux(返回0 - n个元素)
而消费者则是Spring框架帮我们去完成
2、简单的例子构造
首先创建项目,在pom.xml
引入相关依赖,也可以在常见时勾选Spring Reactive Web
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
新建文件
@RestController
public class HelloController {
private static final Logger log = LoggerFactory.getLogger(HelloController.class);
//普通方法
@GetMapping("/hello")
public String hello() {
log.info("开始时间:" + System.currentTimeMillis());
String result = createStr();
log.info("结束时间:" + System.currentTimeMillis());
return result;
}
// Mono方法
@GetMapping("/mono")
public Mono<String> mono() {
log.info("开始时间:" + System.currentTimeMillis());
Mono<String> result = Mono.fromSupplier(this::createStr);
log.info("结束时间:" + System.currentTimeMillis());
return result;
}
// 服务器推送(SSE - >Server Send Event)
@GetMapping(value = "/flux",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> flux() {
Flux<String> result = Flux
.fromStream(IntStream.range(1,5).mapToObj(i->{
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException ignored){
}
return "flux data--"+ i;
}));
return result;
}
// 阻塞5秒钟
private String createStr() {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello world";
}
}
对于前两个方法来说,从调用者(浏览器)的角度而言,是感知不到有什么变化的,因为都是得等待5s才返回数据。但是,从服务端的日志我们可以看出,WebFlux是直接返回Mono对象的(而不是像SpringMVC一直同步阻塞5s,线程才返回)。这正是WebFlux的好处:能够以固定的线程来处理高并发(充分发挥机器的性能)。
第三个方法是WebFlux支持服务器推送(SSE - >Server Send Event),即从服务器推送到客户端。
三、WebFlux与数据库
1、介绍
Spring Boot 2.3.0.RELEASE 开始才正式支持基于 R2DBC 的 MySQL 驱动。
R2DBC: R2DBC 是 Spring 官方在 Spring5 发布了响应式 Web 框架 Spring WebFlux 之后急需能够满足异步响应的数据库交互 API,不过由于缺乏标准和驱动,Pivotal 团队开始自己研究响应式关系型数据库连接 Reactive Relational Database Connectivity,并提出了 R2DBC 规范 API 用来评估可行性并讨论数据库厂商是否有兴趣支持响应式的异步非阻塞驱动程序。最早只有 PostgreSQL 、H2、MSSQL 三家数据库厂商,现在 MySQL也加入进来了。
2、r2dbc实战前期准备
2.1 引入依赖与配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc-mysql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
yml配置连接
server:
port: 8080
spring:
r2dbc:
username: root
password: root
url: r2dbcs:mysql://localhost:3306/webflux?characterEncoding=utf-8&serverTimezone=GMT%2B8
2.2 创建数据表
CREATE TABLE `test_user` (
`user_id` int NOT NULL AUTO_INCREMENT,
`user_name` varchar(255),
`age` int,
`address` varchar(255) ,
PRIMARY KEY (`user_id`) USING BTREE
)
2.3 创建相关类
实体类
@Table("test_user")
public class User {
@Id
private int userId;
private String userName;
private int age;
private String address;
// 省略 getter、setter
}
UserRepository
,就相当于 DAO
public interface UserRepository extends ReactiveCrudRepository<User, Integer> {
}
3、WebFlux接口的两种方式
3.1 基于注解的方式
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserRepository userRepository;
@RequestMapping("/getAllUser")
public Flux<User> getAllUser() {
Flux<User> userFlux = userRepository.findAll();
return userFlux;
}
@RequestMapping("/addUser")
public Mono<User> addUser(User user) {
Mono<User> mono = userRepository.save(user);
return mono;
}
}
3.2 基于Functional Endpoints方式
-
这个配置类的作用有点像
SpringMVC
中的DispatcherServlet
,负责请求的分发,根据不同的请求 URL,找到对应的处理器去处理。 -
通过
RouterFunctions
这样一个工具类来创建RouterFunction
实例。 -
首先调用 nest 方法,第一个参数配置的相当于是接下来配置的地址的一个前缀,这有点类似于我们在 Controller 类上直接写 @
RequestMapping
注解去配置地址。 -
nest
方法的第二个参数就是RouterFunction
实例了,每一个RouterFunction
实例通过RouterFunctions.route
方法来构建,它的第一个参数就是请求的 URL 地址(注意这个时候配置的地址都是有一个共同的前缀),第二个参数我们通过方法引用的方式配置了一个HandlerFunction
,这个就是当前请求的处理器了。 -
通过
addRoute
方法可以配置多个路由策略。
创建 Userhandler.java
package com.example.webflux.controller;
import com.example.webflux.dao.UserRepository;
import com.example.webflux.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.notFound;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
/**
* @author shawn
* @version 1.0
* @date 2022/4/22 16:04
*/
@Component
public class Userhandler {
@Autowired
UserRepository userRepository;
public Mono<ServerResponse> getAllUsers(ServerRequest serverRequest) {
return ok().contentType(APPLICATION_JSON)
.body(userRepository.findAll(), User.class);
}
public Mono<ServerResponse> addUser(ServerRequest serverRequest) {
return ok().contentType(APPLICATION_JSON)
.body(userRepository.saveAll(serverRequest.bodyToMono(User.class)), User.class);
}
public Mono<ServerResponse> deleteUser(ServerRequest serverRequest) {
return userRepository.findById(Integer.valueOf(serverRequest.pathVariable("userId")))
.flatMap(user -> userRepository.delete(user).then(ok().build()))
.switchIfEmpty(notFound().build());
}
}
创建 RouterConfiguration
@Configuration
public class RouterConfiguration {
@Bean
RouterFunction<ServerResponse> userRouterFunction(UserHandler userHandler) {
return RouterFunctions.nest(RequestPredicates.path("/user"),
RouterFunctions.route(RequestPredicates.GET("/getAllUser"), userHandler::getAllUsers)
.andRoute(RequestPredicates.POST("/addUser"), userHandler::addUser)
.andRoute(RequestPredicates.DELETE("/{userId}"), userHandler::deleteUser));
}
}
四、总结
-
如果原先使用用SpringMVC好好的话,则没必要迁移。因为命令式编程是编写、理解和调试代码的最简单方法。因为老项目的类库与代码都是基于阻塞式的。
-
如果你的团队打算使用非阻塞式web框架,WebFlux确实是一个可考虑的技术路线,而且它支持类似于SpringMvc的Annotation的方式实现编程模式,也可以在微服务架构中让WebMvc与WebFlux共用Controller,切换使用的成本相当小。
-
在SpringMVC项目里如果需要调用远程服务的话,你不妨考虑一下使用WebClient,而且方法的返回值可以考虑使用Reactive Type类型的,当每个调用的延迟时间越长,或者调用之间的相互依赖程度越高,其好处就越大。
-
在微服务架构中,您可以混合使用Spring MVC或Spring WebFlux控制器或Spring WebFlux功能端点的应用程序。在两个框架中支持相同的基于注释的编程模型,可以更轻松地重用知识,同时为正确的工作选择正确的工具。
Spring WebFlux
并不是让你的程序运行的更快(相对于SpringMVC来说),而是在有限的资源下提高系统的伸缩性,因此当你对响应式编程非常熟练的情况下并将其应用于新的系统中,还是值得考虑的,否则还是老老实实的使用WebMVC。
参考文献