Spring WebFlux 响应式编程:构建高性能Web应用
这是文章《春季 WebFlux – 春季响应式编程》的第1部分(共3部分)。
Spring WebFlux 是 Spring 5 引入的新模块,标志着 Spring 框架迈向响应式编程模型的第一步。
响应式编程
响应式编程是一种以事件驱动和异步流处理为基础的编程范式,它能提供高效且灵活的编程体验。如果您对响应式编程模型尚不熟悉,强烈建议您阅读以下文章进行学习:
如果您是初次接触 Spring 5,那么请仔细阅读 Spring 5 的特点。
Spring WebFlux 核心概念

-
- Mono:实现了 Publisher 接口,并返回 0 或 1 个元素。
- Flux:实现了 Publisher 接口,并返回 N 个元素。
Spring WebFlux Hello World 示例

Spring WebFlux 的 Maven 依赖项
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.Olivia.spring</groupId>
<artifactId>SpringWebflux</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring WebFlux</name>
<description>Spring WebFlux 示例</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<jdk.version>1.9</jdk.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- 从仓库查找父级 -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</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>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring 快照仓库</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring 里程碑仓库</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring 快照插件仓库</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring 里程碑插件仓库</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
最重要的依赖是 spring-boot-starter-webflux
和 spring-boot-starter-parent
。其他依赖项主要用于创建 JUnit 测试用例。
Spring WebFlux 处理器
Spring WebFlux 处理器方法
Spring WebFlux 处理器方法用于处理请求,并以 Mono
或 Flux
作为响应返回。
package com.Olivia.spring.component;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component
public class HelloWorldHandler {
public Mono<ServerResponse> helloWorld(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromObject("Hello World!"));
}
}
请注意,响应式组件 Mono
保存了 ServerResponse
的主体。同时,观察函数链如何设置返回的内容类型、响应代码和主体。
Spring WebFlux 路由器方法
Spring WebFlux 路由器方法用于定义应用程序的路由。这些方法返回一个同时包含服务器响应主体的 RouterFunction
对象。
package com.Olivia.spring.component;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
@Configuration
public class HelloWorldRouter {
@Bean
public RouterFunction<ServerResponse> routeHelloWorld(HelloWorldHandler helloWorldHandler) {
return RouterFunctions.route(RequestPredicates.GET("/helloWorld")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), helloWorldHandler::helloWorld);
}
}
因此,我们为 /helloWorld
暴露了一个 GET 方法,客户端调用时应接受纯文本响应。
Spring Boot 应用程序
现在,让我们使用 Spring Boot 配置我们简单的 WebFlux 应用程序。
package com.Olivia.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
如果您查看上面的代码,会发现它与 Spring WebFlux 并没有直接关联。但是,由于我们添加了 spring-boot-starter-webflux
模块的依赖,Spring Boot 将自动配置我们的应用程序以支持 Spring WebFlux。
支持 Java 9 模块
我们的应用程序已准备好在 Java 8 上执行,但如果您使用的是 Java 9,我们还需要添加 module-info.java
类。
module com.Olivia.spring {
requires reactor.core;
requires spring.web;
requires spring.beans;
requires spring.context;
requires spring.webflux;
requires spring.boot;
requires spring.boot.autoconfigure;
exports com.Olivia.spring;
}
运行 Spring WebFlux Spring Boot 应用程序

2018-05-07 15:01:47.893 INFO 25158 --- [ main] o.s.w.r.f.s.s.RouterFunctionMapping : Mapped ((GET && /helloWorld) && Accept: [text/plain]) -> com.Olivia.spring.component.HelloWorldRouter$$Lambda$501/704766954@6eeb5d56
2018-05-07 15:01:48.495 INFO 25158 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext : Started HttpServer on /0:0:0:0:0:0:0:0:8080
2018-05-07 15:01:48.495 INFO 25158 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080
2018-05-07 15:01:48.501 INFO 25158 --- [ main] com.Olivia.spring.Application : Started Application in 1.86 seconds (JVM running for 5.542)
根据日志显示,我们的应用程序正在 8080 端口上运行于 Netty 服务器上。让我们继续测试我们的应用程序吧。
Spring WebFlux 应用程序测试
我们可以用多种方法测试我们的应用程序。
1. 使用CURL命令
$ curl https://localhost:8080/helloWorld
Hello World!
$
2. 在浏览器中打开URL
直接在浏览器中访问 https://localhost:8080/helloWorld
。
3. 使用Spring 5的WebTestClient进行单元测试
以下是一个使用Spring 5响应式 WebTestClient
测试REST Web服务的JUnit测试示例:
package com.Olivia.spring;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringWebFluxTest {
@Autowired
private WebTestClient webTestClient;
@Test
public void testHelloWorld() {
webTestClient
.get().uri("/helloWorld") // GET方法和URI
.accept(MediaType.TEXT_PLAIN) // 设置ACCEPT内容类型
.exchange() // 发送请求并获取响应
.expectStatus().isOk() // 检查响应状态是否为OK (200)
.expectBody(String.class).isEqualTo("Hello World!"); // 检查响应体类型和内容是否一致
}
}
将其作为一个JUnit测试用例运行,应该会顺利通过。
4. 使用Spring Web Reactive的WebClient调用REST Web服务
WebClient
也可以用于调用REST Web服务,以下是一个示例:
package com.Olivia.spring.client;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class HelloWorldWebClient {
public static void main(String[] args) {
WebClient client = WebClient.create("https://localhost:8080");
Mono<String> result = client.get()
.uri("/helloWorld")
.accept(MediaType.TEXT_PLAIN)
.retrieve() // 替换 exchange(),更简洁地获取响应体
.bodyToMono(String.class); // 将响应体转换为Mono<String>
System.out.println("结果 = " + result.block());
}
}
将其作为一个简单的Java应用程序运行,你应该能看到正确的输出和大量的调试消息。
总结
在这篇文章中,我们学习了Spring WebFlux以及如何构建一个“Hello World”响应式RESTful Web服务。看到像Spring这样的流行框架也支持响应式编程模型,真是令人高兴。但是我们还有很多需要处理,因为如果你的所有依赖都不是响应式且非阻塞的,那么你的应用程序也不是真正的响应式。例如,关系型数据库供应商并没有提供响应式的驱动程序,因为它们依赖于JDBC,而JDBC不是响应式的。因此,Hibernate API也是非响应式的。所以,如果你使用的是关系型数据库,那么你目前还无法构建一个真正的响应式应用程序。但我希望这将会很快改变。
你可以从我的GitHub仓库下载项目代码。