春天WebFlux是春天5所引入的新模块,是春天WebFlux是春天框架中的反应式编程模型的第一步。
春季反应式编程
如果你是新的反应式编程模型,那么我强烈建议你通过以下文章了解反应式编程。
如果你是春天5的新人,请通过( / 社区 / 教程 / 春天5)。
春天的WebFlux
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 个元素
- 流程:执行 Publisher 并返回 N 个元素
春天WebFlux Hello World 例子
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.
春季 WebFlux Maven 依赖
1<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">
2 <modelVersion>4.0.0</modelVersion>
3 <groupId>com.journaldev.spring</groupId>
4 <artifactId>SpringWebflux</artifactId>
5 <version>0.0.1-SNAPSHOT</version>
6 <name>Spring WebFlux</name>
7 <description>Spring WebFlux Example</description>
8
9 <properties>
10 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
11 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
12 <jdk.version>1.9</jdk.version>
13 </properties>
14
15 <parent>
16 <groupId>org.springframework.boot</groupId>
17 <artifactId>spring-boot-starter-parent</artifactId>
18 <version>2.0.1.RELEASE</version>
19 <relativePath/> <!-- lookup parent from repository -->
20 </parent>
21
22 <dependencies>
23 <dependency>
24 <groupId>org.springframework.boot</groupId>
25 <artifactId>spring-boot-starter-webflux</artifactId>
26 </dependency>
27 <dependency>
28 <groupId>org.springframework.boot</groupId>
29 <artifactId>spring-boot-starter-test</artifactId>
30 <scope>test</scope>
31 </dependency>
32
33 <dependency>
34 <groupId>io.projectreactor</groupId>
35 <artifactId>reactor-test</artifactId>
36 <scope>test</scope>
37 </dependency>
38 </dependencies>
39 <repositories>
40 <repository>
41 <id>spring-snapshots</id>
42 <name>Spring Snapshots</name>
43 <url>https://repo.spring.io/snapshot</url>
44 <snapshots>
45 <enabled>true</enabled>
46 </snapshots>
47 </repository>
48 <repository>
49 <id>spring-milestones</id>
50 <name>Spring Milestones</name>
51 <url>https://repo.spring.io/milestone</url>
52 <snapshots>
53 <enabled>false</enabled>
54 </snapshots>
55 </repository>
56 </repositories>
57 <pluginRepositories>
58 <pluginRepository>
59 <id>spring-snapshots</id>
60 <name>Spring Snapshots</name>
61 <url>https://repo.spring.io/snapshot</url>
62 <snapshots>
63 <enabled>true</enabled>
64 </snapshots>
65 </pluginRepository>
66 <pluginRepository>
67 <id>spring-milestones</id>
68 <name>Spring Milestones</name>
69 <url>https://repo.spring.io/milestone</url>
70 <snapshots>
71 <enabled>false</enabled>
72 </snapshots>
73 </pluginRepository>
74 </pluginRepositories>
75 <build>
76 <plugins>
77 <plugin>
78 <groupId>org.springframework.boot</groupId>
79 <artifactId>spring-boot-maven-plugin</artifactId>
80 </plugin>
81 </plugins>
82 <pluginManagement>
83 <plugins>
84 <plugin>
85 <groupId>org.apache.maven.plugins</groupId>
86 <artifactId>maven-compiler-plugin</artifactId>
87 <version>3.7.0</version>
88 <configuration>
89 <source>${jdk.version}</source>
90 <target>${jdk.version}</target>
91 </configuration>
92 </plugin>
93 </plugins>
94 </pluginManagement>
95 </build>
96
97</project>
最重要的依赖性是春起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起起的依赖性
。
春季 WebFlux 处理器
Spring WebFlux 处理方法处理请求并返回Mono
或Flux
作为响应。
1package com.journaldev.spring.component;
2
3import org.springframework.http.MediaType;
4import org.springframework.stereotype.Component;
5import org.springframework.web.reactive.function.BodyInserters;
6import org.springframework.web.reactive.function.server.ServerRequest;
7import org.springframework.web.reactive.function.server.ServerResponse;
8
9import reactor.core.publisher.Mono;
10
11@Component
12public class HelloWorldHandler {
13
14 public Mono<ServerResponse> helloWorld(ServerRequest request) {
15 return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
16 .body(BodyInserters.fromObject("Hello World!"));
17 }
18}
請注意,反應元件「Mono」持有「ServerResponse」體內,並查看函數鏈以設定返回內容類型、回應碼和體內。
春天 WebFlux 路由器
路由器方法用于定义应用程序的路径,这些方法返回具有ServerResponse
体的RouterFunction
对象。
1package com.journaldev.spring.component;
2
3import org.springframework.context.annotation.Bean;
4import org.springframework.context.annotation.Configuration;
5import org.springframework.http.MediaType;
6import org.springframework.web.reactive.function.server.RequestPredicates;
7import org.springframework.web.reactive.function.server.RouterFunction;
8import org.springframework.web.reactive.function.server.RouterFunctions;
9import org.springframework.web.reactive.function.server.ServerResponse;
10
11@Configuration
12public class HelloWorldRouter {
13
14 @Bean
15 public RouterFunction<ServerResponse> routeHelloWorld(HelloWorldHandler helloWorldHandler) {
16
17 return RouterFunctions.route(RequestPredicates.GET("/helloWorld")
18 .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), helloWorldHandler::helloWorld);
19 }
20}
因此,我们暴露了 / helloWorld
的GET方法,客户端呼叫应该接受简单的文本响应。
春运应用程序
让我们用 Spring Boot 配置我们的简单 WebFlux 应用程序。
1package com.journaldev.spring;
2
3import org.springframework.boot.SpringApplication;
4import org.springframework.boot.autoconfigure.SpringBootApplication;
5
6@SpringBootApplication
7public class Application {
8
9 public static void main(String[] args) {
10 SpringApplication.run(Application.class, args);
11 }
12}
如果你看上面的代码,它与Spring WebFlux无关,但是Spring Boot将将我们的应用程序配置为Spring WebFlux,因为我们已经添加了spring-boot-starter-webflux
模块的依赖性。
Java 9 模块支持
我们的应用程序已准备好在Java 8上运行,但如果您正在使用Java 9,我们还需要添加module-info.java
类。
1module com.journaldev.spring {
2 requires reactor.core;
3 requires spring.web;
4 requires spring.beans;
5 requires spring.context;
6 requires spring.webflux;
7 requires spring.boot;
8 requires spring.boot.autoconfigure;
9 exports com.journaldev.spring;
10}
运行 Spring WebFlux 春季启动应用程序
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.
12018-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
22018-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
32018-05-07 15:01:48.495 INFO 25158 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080
42018-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 服务器上运行。
春季WebFlux应用程序测试
我们可以通过各种方法测试我们的应用程序。
- Using CURL command
1$ curl https://localhost:8080/helloWorld 2Hello World! 3$
- Launch URL in Browser
- Using WebTestClient from Spring 5 Here is a JUnit test program to test our Rest web service using
WebTestClient
from Spring 5 reactive web.1package com.journaldev.spring; 2 3import org.junit.Test; 4import org.junit.runner.RunWith; 5import org.springframework.beans.factory.annotation.Autowired; 6import org.springframework.boot.test.context.SpringBootTest; 7import org.springframework.http.MediaType; 8import org.springframework.test.context.junit4.SpringRunner; 9import org.springframework.test.web.reactive.server.WebTestClient; 10 11@RunWith(SpringRunner.class) 12@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 13public class SpringWebFluxTest { 14 15 @Autowired 16 private WebTestClient webTestClient; 17 18 19 @Test 20 public void testHelloWorld() { 21 webTestClient 22 .get().uri("/helloWorld") // GET method and URI 23 .accept(MediaType.TEXT_PLAIN) //setting ACCEPT-Content 24 .exchange() //gives access to response 25 .expectStatus().isOk() //checking if response is OK 26 .expectBody(String.class).isEqualTo("Hello World!"); // checking for response type and message 27 } 28 29} 30```Run it a JUnit test case and it should pass with flying colors. 
- Using WebClient from Spring Web Reactive We can also use
WebClient
to call the REST web service.1package com.journaldev.spring.client; 2 3import org.springframework.http.MediaType; 4import org.springframework.web.reactive.function.client.ClientResponse; 5import org.springframework.web.reactive.function.client.WebClient; 6 7import reactor.core.publisher.Mono; 8 9public class HelloWorldWebClient { 10 11 public static void main(String args[]) { 12 WebClient client = WebClient.create("https://localhost:8080"); 13 14 Mono<ClientResponse> result = client.get() 15 .uri("/helloWorld") 16 .accept(MediaType.TEXT_PLAIN) 17 .exchange(); 18 19 System.out.println("Result = " + result.flatMap(res -> res.bodyToMono(String.class)).block()); 20 } 21 22} 23```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 Reactive Restful Web服务,很高兴看到像Spring这样的流行的框架正在为反应式编程模型扎根,但我们有很多要覆盖的,因为如果你的所有依赖不是反应式的,而不是阻断的,那么你的应用程序也不是真正反应性的。例如,关系数据库供应商没有反应式驱动程序,因为他们依赖于JDBC,这不是反应式的。
您可以从我的 GitHub 存储库下载项目代码。
参考: 官方文件