实战Spring Boot 2.0 Reactive编程系列 - WebFlux初体验
前言
上文引入了 反应式编程模型 相关概念,对 Spring Reactor
的核心 API
进行了简单归纳。本文会对 Spring 5 WebFlux
进行相关介绍,包括引入 Servlet 3.1 +
,各个功能组件 Router Functions
、WebFlux
和 Reactive Streams
等,以及如何在 Spring Boot 2.0
中分别以 全局功能路由 和 MVC
控制器 的方式配置 HTTP
请求处理。

正文
Spring 5 WebFlux介绍
关于 Spring 5
的 WebFlux
响应式编程,下图是传统 Spring Web MVC
结构以及Spring 5
中新增加的基于 Reactive Streams
的 Spring WebFlux
框架。可以使用 webFlux
模块来构建 异步的、非堵塞的、事件驱动 的服务,其在 伸缩性方面 表现非常好。

如图所示,WebFlux
模块从上到下依次是 Router Functions
、WebFlux
、Reactive Streams
三个新组件。
Servlet 3.1+ API介绍
WebFlux
模块需要运行在实现了 Servlet 3.1+
规范 的容器之上。Servlet 3.1
规范中新增了对 异步处理 的支持,在新的 Servlet
规范中,Servlet
线程不需要一直 阻塞等待 到业务处理完成。
在 Servlet 3.1
中,其请求处理的线程模型大致如下:
-
Servlet
线程接收到新的请求后,不需要等待业务处理完成再进行结果输出,而是将这个请求委托给另外一个线程(业务线程)来完成。 -
Servlet
线程将委托完成之后变返回到容器中去接收新的请求。
Servlet 3.1
规范特别适用于那种 业务处理非常耗时 的场景之下,可以减少 服务器资源 的占用,并且提高 并发处理速度 ,而对于那些能 快速响应 的场景收益并不大。
所以 WebFlux
支持的容器有 Tomcat
、Jetty
等 同步容器 ,也可以是 Netty
和 Undertow
这类 异步容器。在容器中 Spring WebFlux
会将 输入流 适配成 Mono
或 Flux
格式进行统一处理。
Spring WebFlux的功能模块
下面介绍上图中 WebFlux
各个模块:
1. Router Functions
对标准的 @Controller
,@RequestMapping
等的 Spring MVC
注解,提供一套 函数式风格 的 API
,用于创建 Router
、Handler
和Filter
。
2. WebFlux
核心组件,协调上下游各个组件提供 响应式编程 支持。
3. Reactive Streams
一种支持 背压 (Backpressure)
的 异步数据流处理标准,主流实现有 RxJava
和 Reactor
,Spring WebFlux
集成的是Reactor
。
Flux
Flux
和 Mono
属于 事件发布者,类似于 生产者,对消费者 提供订阅接口。当有事件发生的时候,Flux
或 Mono
会回调 消费者相应的方法来通知 消费者 相应的事件。
下面这张图是 Flux
的工作流程图:

关于 Flux
的工作模式,可以看出 Flux
可以 触发 (emit)
很多 item
,而这些 item
可以经过若干 Operators
然后才被 subscribe
,下面是使用 Flux
的一个例子:
Flux.fromIterable(getSomeLongList())
.mergeWith(Flux.interval(100))
.doOnNext(serviceA::someObserver)
.map(d -> d * 2)
.take(3)
.onErrorResumeWith(errorHandler::fallback)
.doAfterTerminate(serviceM::incrementTerminate)
.subscribe(System.out::println);
Mono
下面的图片是 Mono
的处理流程,可以很直观的看出来 Mono
和 Flux
的区别:

Mono
只能触发 (emit)
一个 item
,下面是使用 Mono
的一个例子:
Mono.fromCallable(System::currentTimeMillis)
.flatMap(time -> Mono.first(serviceA.findRecent(time), serviceB.findRecent(time)))
.timeout(Duration.ofSeconds(3), errorHandler::fallback)
.doOnSuccess(r -> serviceM.incrementSuccess())
.subscribe(System.out::println);
Spring Boot 2.0 Reactive Stack
Spring Boot Webflux
就是基于 Reactor
实现的。Spring Boot 2.0
包括一个新的 spring-webflux
模块。该模块包含对 响应式 HTTP
和 WebSocket
客户端的支持,以及对 REST
、HTML
和 WebSocket
交互等程序 的支持。一般来说,Spring MVC
用于 同步处理,Spring Webflux
用于 异步处理。

如上图所示,从 Web
表现层到数据访问,再到容器,Spring Boot 2.0
同时提供了 同步阻塞式 和 异步非阻塞式 两套完整的 API Stack
。
从上而下对比以下两者的区别:
API Stack | Sevlet Stack | Reactive Stack |
---|---|---|
Web控制层 | Spring MVC | Spring WebFlux |
安全认证层 | Spring Security | Spring Security |
数据访问层 | Spring Data Repositories | Spring Data Reactive Repositories |
容器API | Servlet API | Reactive Streams Adapters |
内嵌容器 | Servlet Containers | Netty, Servlet 3.1+ Containers |
适用场景
控制层一旦使用 Spring WebFlux
,它下面的安全认证层、数据访问层都必须使用 Reactive API
。其次,Spring Data Reactive Repositories
目前只支持 MongoDB
、Redis
和 Couchbase
等几种不支持事务管理的 NOSQL
。技术选型时一定要权衡这些弊端和风险,比如:
-
Spring MVC
能满足场景的,就不需要更改为Spring WebFlux
。 -
要注意容器的支持,可以看看底层 内嵌容器 的支持。
-
微服务 体系结构,
Spring WebFlux
和Spring MVC
可以混合使用。尤其开发IO
密集型 服务的时候,可以选择Spring WebFlux
去实现。
编程模型
Spring 5 Web
模块包含了 Spring WebFlux
的 HTTP
抽象。类似 Servlet API
, WebFlux
提供了 WebHandler API
去定义 非阻塞 API
抽象接口。可以选择以下两种编程模型实现:
-
注解控制层: 和
MVC
保持一致,WebFlux
也支持 响应性@RequestBody
注解。 -
功能性端点: 基于
lambda
轻量级编程模型,用来 建立路由 和 处理请求 的工具。和上面最大的区别就是,这种模型,全程 控制了 请求 - 响应 的生命流程
内嵌容器
跟 Spring Boot
大框架一样启动应用,但 Spring WebFlux
默认是通过 Netty
启动,并且自动设置了 默认端口 为 8080
。另外还提供了对 Jetty
、Undertow
等容器的支持。开发者自行在添加对应的容器 Starter
组件依赖,即可配置并使用对应 内嵌容器实例。
注意: 必须是 Servlet 3.1+ 容器,如 Tomcat、Jetty;或者非 Servlet 容器,如 Netty 和 Undertow。
Starter 组件
跟 Spring Boot
大框架一样,Spring Boot Webflux
提供了很多 开箱即用 的 Starter
组件。添加 spring-boot-starter-webflux
依赖,就可用于构建 响应式 API
服务,其包含了 WebFlux
和 Tomcat
内嵌容器 等。
快速开始
Spring Initializr构建项目骨架
利用 Spring Initializer
快速生成 Spring Boot
应用,配置项目信息并设置依赖。
配置Maven依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Spring Boot启动类
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
配置实体类
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Message {
String body;
}
1. MVC控制器方式
1.1. 编写控制器
@RestController
@RequestMapping
public class MessageController {
@GetMapping
public Flux<Message> allMessages(){
return Flux.just(
Message.builder().body("hello Spring 5").build(),
Message.builder().body("hello Spring Boot 2").build()
);
}
}
1.2. 编写测试类
@RunWith(SpringRunner.class)
@WebFluxTest(controllers = MessageController.class)
public class DemoApplicationTests {
@Autowired
WebTestClient client;
@Test
public void getAllMessagesShouldBeOk() {
client.get().uri("/").exchange().expectStatus().isOk();
}
}
1.3. 查看启动日志
2018-05-27 17:37:23.550 INFO 67749 --- [ main] s.w.r.r.m.a.RequestMappingHandlerMapping : Mapped "{[],methods=[GET]}" onto reactor.core.publisher.Flux<com.example.demo.Message> com.example.demo.MessageController.allMessages()
2018-05-27 17:37:23.998 INFO 67749 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext : Started HttpServer on /0:0:0:0:0:0:0:0:8080
2018-05-27 17:37:23.999 INFO 67749 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080
2018-05-27 17:37:24.003 INFO 67749 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.6 seconds (JVM running for 2.274)
从日志里可以看出:
- 启动时
WebFlux
利用MVC
原生的RequestMappingHandlerMapping
将控制器里的 请求路径 和MVC
中的 处理器 进行绑定。 Spring WebFlux
默认采用Netty
作为 内嵌容器,且启动端口默认为8080
。
访问 http://localhost:8080,返回结果如下:

2. 全局Router API方式
2.1. 配置全局Router Bean
@Configuration
public class DemoRouterConfig {
@Bean
public RouterFunction<ServerResponse> routes() {
return route(GET("/"), (ServerRequest req)-> ok()
.body(
BodyInserters.fromObject(
Arrays.asList(
Message.builder().body("hello Spring 5").build(),
Message.builder().body("hello Spring Boot 2").build()
)
)
)
);
}
}
2.2. 编写测试类
@RunWith(SpringRunner.class)
@WebFluxTest
public class DemoApplicationTests {
@Autowired
WebTestClient client;
@Test
public void getAllMessagesShouldBeOk() {
client.get().uri("/").exchange().expectStatus().isOk();
}
}
2.3. 查看启动日志
运行 Spring Boot
启动入口类,启动日志如下(不重要的省略):
2018-05-27 17:20:28.870 INFO 67696 --- [ main] o.s.w.r.f.s.s.RouterFunctionMapping : Mapped (GET && /) -> com.example.demo.DemoRouterConfig$$Lambda$213/1561745898@3381b4fc
2018-05-27 17:20:28.931 INFO 67696 --- [ main] o.s.w.r.r.m.a.ControllerMethodResolver : Looking for @ControllerAdvice: org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext@1460a8c0: startup date [Sun May 27 17:20:27 CST 2018]; root of context hierarchy
2018-05-27 17:20:29.311 INFO 67696 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext : Started HttpServer on /0:0:0:0:0:0:0:0:8080
2018-05-27 17:20:29.312 INFO 67696 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080
2018-05-27 17:20:29.316 INFO 67696 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 2.137 seconds (JVM running for 3.169)
从日志里可以看出:启动时 WebFlux
利用 RouterFunctionMapping
将 RouterFunction
里的 全局路径 和 请求处理 进行了映射和绑定。
访问 http://localhost:8080,返回结果如下:

可以看出,无论是使用 Fucntional Router
还是 MVC Controller
,都可以产生相同的效果!
开发运行环境
-
JDK 1.8 + :
Spring Boot 2.x
要求JDK 1.8
环境及以上版本。另外,Spring Boot 2.x
只兼容Spring Framework 5.0
及以上版本。 -
Maven 3.2 + : 为
Spring Boot 2.x
提供了相关依赖构建工具是Maven
,版本需要3.2
及以上版本。使用Gradle
则需要1.12
及以上版本。Maven
和Gradle
大家各自挑选下喜欢的就好。
小结
本文首先对 Spring 5 WebFlux
进行了单独介绍,包括引入 Servlet 3.1 +
,各个功能组件 Router Functions
、WebFlux
和 Reactive Streams
等。然后在 Spring Boot 2.0
详细地介绍了 Reactive Stack
和 Servlet Stack
的组成区别,并分别给出了 WebFlux
基于 全局功能路由 和 控制器 的配置和使用案例。
欢迎关注技术公众号: 零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。
转载自:https://juejin.cn/post/6844903631141994503