春季 WebFlux – 春季响应式编程
Spring WebFlux是Spring 5引入的新模块。Spring WebFlux是在Spring框架中迈向响应式编程模型的第一步。
响应式编程 是指一种以事件驱动和异步流处理为基础的编程模式,它可以提供高效而灵活的编程体验。
如果你对响应式编程模型还不熟悉,我强烈建议你阅读以下文章来学习响应式编程。
- Reactive Manifesto
- Reactive Streams
- Java 9 Reactive Streams
- RxJava
如果你是初次接触Spring 5,那么请仔细阅读Spring 5的特点。
春天的WebFlux

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

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 Example</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/> <!-- lookup parent from repository -->
</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 Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</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测试用例的。
春季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 路由器
路由器方法用于定义应用的路由。这些方法返回一个同时包含服务器响应主体的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配置我们简单的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 应用程序测试
我们可以用多种方法测试我们的应用程序。
-
- 使用CURL命令
-
- $ curl https://localhost:8080/helloWorld
-
- Hello World!
-
- $
在浏览器中打开URL
使用Spring 5的WebTestClient,在此处是一个JUnit测试程序,使用Spring 5的响应式WebTestClient来测试我们的REST Web服务。
com.Olivia.spring包;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
@RunWith(SpringRunner.class)
@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
.expectBody(String.class).isEqualTo(“Hello World!”); //检查响应类型和消息是否一致
}
}
将其作为一个JUnit测试用例运行,应该会顺利通过。
使用Spring Web Reactive的WebClient也可以调用REST Web服务。
com.Olivia.spring.client包;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
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 result = client.get()
.uri(“/helloWorld”)
.accept(MediaType.TEXT_PLAIN)
.exchange();
System.out.println(“Result = ” + result.flatMap(res -> res.bodyToMono(String.class)).block());
}
}
将其作为一个简单的Java应用程序运行,你应该能看到正确的输出和大量的调试消息。
总结
在这篇文章中,我们学习了Spring WebFlux以及如何构建一个“Hello World”反应式的Restful Web服务。看到像Spring这样的流行框架也支持反应式编程模型,真是令人高兴。但是我们还有很多需要处理,因为如果你的所有依赖都不是反应式且非阻塞的,那么你的应用程序也不是真正的反应式。例如,关系型数据库供应商并没有提供反应式的驱动程序,因为他们依赖于JDBC,而JDBC不是反应式的。因此,Hibernate API也是非反应式的。所以,如果你使用的是关系型数据库,那么你目前还无法构建一个真正的反应式应用程序。但我希望这将会很快改变。
你可以从我的GitHub仓库下载项目代码。
参考:官方文档