Spring WebFlux - Spring 反应式编程
Spring WebFlux 是 Spring 5 中引入的新模块。Spring WebFlux 是 Spring 框架迈向响应式编程模型的第一步。
Spring 反应式编程
如果您不熟悉反应式编程模型,那么我强烈建议您阅读以下文章来了解反应式编程。
如果您是 Spring 5 新手,请浏览Spring 5 功能。
Spring WebFlux
Spring WebFlux 是Spring MVC模块的替代品。Spring WebFlux 用于创建基于事件循环执行模型的完全异步和非阻塞应用程序。下面的图表来自 Spring 官方文档,对 Spring WebFlux 与 Spring Web MVC 的比较提供了很好的见解。如果您希望在非阻塞反应模型上开发 Web 应用程序或 Rest Web 服务,那么您可以研究 Spring WebFlux。Tomcat、Jetty、Servlet 3.1+ 容器以及非 Servlet 运行时(如 Netty 和 Undertow)都支持 Spring WebFlux。Spring WebFlux 建立在Project Reactor上。Project Reactor 是 Reactive Streams 规范的实现。Reactor 提供两种类型:
- Mono:实现 Publisher 并返回 0 或 1 个元素
- Flux:实现 Publisher 并返回 N 个元素。
Spring WebFlux Hello World 示例
让我们构建一个简单的 Spring WebFlux Hello World 应用程序。我们将创建一个简单的 rest web 服务并使用 Spring Boot 在默认 Netty 服务器上运行它。我们的最终项目结构如下图所示。让我们逐一研究应用程序的每个组件。
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.journaldev.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 测试用例。
Spring WebFlux 处理程序
Spring WebFlux Handler 方法处理请求并返回Mono
或Flux
作为响应。
package com.journaldev.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
还包含ServerResponse
主体的对象。
package com.journaldev.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);
}
}
因此,我们公开了一种 GET 方法,/helloWorld
客户端调用应该接受纯文本响应。
Spring Boot 应用程序
让我们用 Spring Boot 配置我们的简单 WebFlux 应用程序。
package com.journaldev.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.journaldev.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.journaldev.spring;
}
运行 Spring WebFlux Spring Boot 应用程序
如果您在 Eclipse 中支持 Spring,那么您可以将上述类作为 Spring Boot App 运行。如果您喜欢使用命令行,请打开终端并mvn spring-boot:run
从项目源目录运行命令。应用程序运行后,请注意以下日志消息,以确保我们的应用程序一切正常。当您通过添加更多路由和功能来扩展这个简单的应用程序时,它也很有帮助。
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.journaldev.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.journaldev.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 测试程序,用于使用
WebTestClient
Spring 5 反应式 Web 测试我们的 Rest Web 服务。package com.journaldev.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 method and URI .accept(MediaType.TEXT_PLAIN) //setting ACCEPT-Content .exchange() //gives access to response .expectStatus().isOk() //checking if response is OK .expectBody(String.class).isEqualTo("Hello World!"); // checking for response type and message } }
运行 JUnit 测试用例,它应该会顺利通过。
-
使用 Spring Web Reactive 中的 WebClient我们还可以用来
WebClient
调用REST Web 服务。package com.journaldev.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<ClientResponse> 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 存储库下载项目代码。
参考:官方文档