Spring WebFlux是Spring 5中引入的新模块。Spring WebFlux是Spring框架中向反应式编程模型迈出的第一步。
如果您是反应式编程模型的新手,那么我强烈建议您阅读以下文章来了解反应式编程。
- 反应式宣言
- 反应式流
- Java 9 反应式流
- RxJava
如果您是 Spring 5 的新手,请浏览Spring 5 特点.
Spring WebFlux is the alternative to Spring MVC module. Spring WebFlux is used to create fully asynchronous and non-blocking application built on event-loop execution model. Below diagram from Spring Official Documentation provides great insight on comparison of Spring WebFlux to Spring Web MVC. If you are looking to develop a web application or Rest web service on non-blocking reactive model, then you can look into Spring WebFlux. Spring WebFlux is supported on Tomcat, Jetty, Servlet 3.1+ containers, as well as on non-Servlet runtimes such as Netty and Undertow. Spring WebFlux is built on Project Reactor. Project Reactor is the implementation of Reactive Streams specification. Reactor provides two types:
-
Mono:实现 Publisher 并返回 0 或 1 个元素
-
Flux:实现 Publisher 并返回 N 个元素。
Let’s built a simple Spring WebFlux Hello World application. We will create a simple rest web service and use Spring Boot to run it on default Netty server. Our final project structure looks like below image. Let’s look into each component of the application one by one.
<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
and spring-boot-starter-parent
。其他一些依赖项用于创建 JUnit 测试用例。
Spring WebFlux Handler方法处理请求并返回Mono
or 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
身体。另请查看函数链以设置返回内容类型、响应代码和正文。
路由器方法用于定义应用程序的路由。这些方法返回RouterFunction
也持有的对象ServerResponse
body.
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 配置简单的 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 会将我们的应用程序配置为 Spring WebFlux,因为我们添加了依赖项spring-boot-starter-webflux
module.
我们的应用程序已准备好在 Java 8 上执行,但如果您使用的是 Java 9,那么我们还需要添加module-info.java
class.
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;
}
If you have Spring support in Eclipse, then you can run above class as Spring Boot App. If you like to use command line, then open terminal and run command mvn spring-boot:run
from the project source directory. Once the app is running, notice following log messages to make sure everything is good with our app. It’s also helpful when you extend this simple app by adding more routes and functionalities.
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)
从日志中可以清楚地看出,我们的应用程序正在 Netty 服务器的端口 8080 上运行。让我们继续测试我们的应用程序。
我们可以使用各种方法来测试我们的应用程序。
-
使用 CURL 命令
$ curl https://localhost:8080/helloWorld
Hello World!
$
-
Launch URL in Browser
-
从 Spring 5 使用 WebTestClient这是一个 JUnit 测试程序,用于使用以下命令来测试我们的 Rest Web 服务WebTestClient
来自 Spring 5 反应式网络。
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
}
}
Run it a JUnit test case and it should pass with flying colors.
-
使用 Spring Web Reactive 中的 WebClient我们还可以使用WebClient
打电话给REST 网络服务.
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());
}
}
Just run it as a simple java application and you should see the proper output with a lot of debug messages.
在这篇文章中,我们了解了 Spring WebFlux 以及如何构建 hello world 反应式 Restful Web 服务。很高兴看到 Spring 等流行框架都支持响应式编程模型。但我们有很多内容要介绍,因为如果您的所有依赖项都不是响应式且非阻塞的,那么您的应用程序也不是真正的响应式。例如,关系数据库供应商没有反应式驱动程序,因为它们依赖于 JDBC,而后者不是反应式的。因此 Hibernate API 也是非响应式的。因此,如果您使用关系数据库,那么您还无法构建真正的反应式应用程序。我希望这种情况能够尽快改变。
您可以从我的下载项目代码GitHub 存储库.
参考:官方文档