实战Spring Boot 2.0 Reactive编程系列 - WebFlux初体验

前言

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

正文

Spring 5 WebFlux介绍

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

如图所示,WebFlux 模块从上到下依次是 Router FunctionsWebFluxReactive Streams 三个新组件。web

Servlet 3.1+ API介绍

WebFlux 模块须要运行在实现了 Servlet 3.1+ 规范 的容器之上。Servlet 3.1 规范中新增了对 异步处理 的支持,在新的 Servlet 规范中,Servlet 线程不须要一直 阻塞等待 到业务处理完成。spring

Servlet 3.1 中,其请求处理的线程模型大体以下:编程

  1. Servlet 线程接收到新的请求后,不须要等待业务处理完成再进行结果输出,而是将这个请求委托给另一个线程(业务线程)来完成。后端

  2. Servlet 线程将委托完成以后变返回到容器中去接收新的请求。缓存

Servlet 3.1 规范特别适用于那种 业务处理很是耗时 的场景之下,能够减小 服务器资源 的占用,而且提升 并发处理速度 ,而对于那些能 快速响应 的场景收益并不大。安全

因此 WebFlux 支持的容器有 TomcatJetty同步容器 ,也能够是 NettyUndertow 这类 异步容器。在容器中 Spring WebFlux 会将 输入流 适配成 MonoFlux 格式进行统一处理。bash

Spring WebFlux的功能模块

下面介绍上图中 WebFlux 各个模块:服务器

1. Router Functions

对标准的 @Controller@RequestMapping等的 Spring MVC 注解,提供一套 函数式风格API,用于建立 RouterHandlerFilter

2. WebFlux

核心组件,协调上下游各个组件提供 响应式编程 支持。

3. Reactive Streams

一种支持 背压 (Backpressure)异步数据流处理标准,主流实现有 RxJavaReactorSpring WebFlux 集成的是Reactor

Flux

FluxMono 属于 事件发布者,相似于 生产者,对消费者 提供订阅接口。当有事件发生的时候,FluxMono 会回调 消费者相应的方法来通知 消费者 相应的事件。

下面这张图是 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 的处理流程,能够很直观的看出来 MonoFlux 的区别:

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 模块。该模块包含对 响应式 HTTPWebSocket 客户端的支持,以及对 RESTHTMLWebSocket 交互等程序 的支持。通常来讲,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 目前只支持 MongoDBRedisCouchbase 等几种不支持事务管理的 NOSQL。技术选型时必定要权衡这些弊端和风险,好比:

  1. Spring MVC 能知足场景的,就不须要更改成 Spring WebFlux

  2. 要注意容器的支持,能够看看底层 内嵌容器 的支持。

  3. 微服务 体系结构,Spring WebFluxSpring MVC 能够混合使用。尤为开发 IO 密集型 服务的时候,能够选择 Spring WebFlux 去实现。

编程模型

Spring 5 Web 模块包含了 Spring WebFluxHTTP 抽象。相似 Servlet APIWebFlux 提供了 WebHandler API 去定义 非阻塞 API 抽象接口。能够选择如下两种编程模型实现:

  1. 注解控制层:MVC 保持一致,WebFlux 也支持 响应性 @RequestBody 注解。

  2. 功能性端点: 基于 lambda 轻量级编程模型,用来 创建路由处理请求 的工具。和上面最大的区别就是,这种模型,全程 控制了 请求 - 响应 的生命流程

内嵌容器

Spring Boot 大框架同样启动应用,但 Spring WebFlux 默认是经过 Netty 启动,而且自动设置了 默认端口8080。另外还提供了对 JettyUndertow 等容器的支持。开发者自行在添加对应的容器 Starter 组件依赖,便可配置并使用对应 内嵌容器实例

注意: 必须是 Servlet 3.1+ 容器,如 Tomcat、Jetty;或者非 Servlet 容器,如 Netty 和 Undertow。

Starter 组件

Spring Boot 大框架同样,Spring Boot Webflux 提供了不少 开箱即用Starter 组件。添加 spring-boot-starter-webflux 依赖,就可用于构建 响应式 API 服务,其包含了 WebFluxTomcat 内嵌容器 等。

快速开始

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)
复制代码

从日志里能够看出:

  1. 启动时 WebFlux 利用 MVC 原生的 RequestMappingHandlerMapping 将控制器里的 请求路径MVC 中的 处理器 进行绑定。
  2. 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 利用 RouterFunctionMappingRouterFunction 里的 全局路径请求处理 进行了映射和绑定。

访问 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 及以上版本。MavenGradle 你们各自挑选下喜欢的就好。

小结

本文首先对 Spring 5 WebFlux 进行了单独介绍,包括引入 Servlet 3.1 +,各个功能组件 Router FunctionsWebFluxReactive Streams 等。而后在 Spring Boot 2.0 详细地介绍了 Reactive StackServlet Stack 的组成区别,并分别给出了 WebFlux 基于 全局功能路由控制器 的配置和使用案例。


欢迎关注技术公众号: 零壹技术栈

零壹技术栈

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

相关文章
相关标签/搜索