Spring Web Flux是spring5中引入的新模块。Spring Web Flux是Spring框架中实现反应式编程模型的第一步。
目录
Spring反应式编程
如果您是反应式编程模型的新手,那么我强烈建议您阅读以下文章来了解反应式编程。
如果你是第五春的新手,请仔细阅读Spring5特色.
SpringWebFlux
SpringWebFlux是SpringMVC模块。Spring Web Flux用于创建基于事件循环执行模型的完全异步和非阻塞应用程序。
下图来自Spring官方文档,提供了SpringWebFlux和SpringWebMVC的比较。
如果您希望在非阻塞反应式模型上开发web应用程序或restweb服务,那么您可以看看Spring Web Flux。
Spring Web Flux在Tomcat、Jetty、servlet3.1+容器以及Netty和Undertow等非Servlet运行时都受支持。
Spring Web Flux是基于项目反应堆. 项目反应器是实现反应流规范。Reactor提供两种类型:
- 单声道:实现Publisher并返回0或1个元素
- 通量:实现Publisher并返回N个元素。
Spring Web Flux Hello World示例
让我们构建一个简单的Spring Web Flux Hello World应用程序。我们将创建一个简单的restweb服务,并使用Spring Boot在默认Netty服务器上运行它。
我们最终的项目结构如下图所示。
让我们逐一查看应用程序的每个组件。
Spring Web Flux-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
and spring-boot-starter-parent
. Some other dependencies are for creating JUnit test cases.
Spring WebFlux处理程序
Spring Web Flux处理程序方法处理请求并返回Mono
or Flux
as response.
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
holds the ServerResponse
body. Also look at the function chain to set the return content type, response code and body.
Spring WebFlux路由器
路由器方法用于定义应用程序的路由。这些方法返回RouterFunction
object that also holds 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
and the client call should accept plain text response.
Spring引导应用程序
让我们用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 Web Flux无关。但是Spring Boot将把我们的应用程序配置为Spring Web Flux,因为我们添加了spring-boot-starter-webflux
module.
Java 9模块支持
我们的应用程序已经准备好在Java8上执行了,但是如果您使用的是Java9,那么我们还需要添加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;
}
运行Spring Web Flux Spring引导应用程序
如果您在Eclipse中支持Spring,那么您可以作为Spring Boot应用程序来运行上面的类。
如果您喜欢使用命令行,那么打开终端并运行命令mvn spring-boot:run
from the project source directory.
一旦应用程序运行,请注意以下日志消息,以确保我们的应用程序一切正常。当您通过添加更多路由和功能来扩展这个简单的应用程序时,它也很有帮助。
2018-05-07 15:01:47.893 INFO 25158 --- [ main] o.s.w.r.f.s.s.RouterFunctionMapping : Mapped ((GET && /helloWorld) && Accept: ) -> 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上。让我们继续测试我们的应用程序。
SpringWebFlux应用测试
我们可以用各种方法测试我们的应用程序。
- 使用CURL命令
$ curl https://localhost:8080/helloWorld Hello World! $
- 在浏览器中启动URL
- 使用spring5中的WebTestClient
下面是一个JUnit测试程序,它使用
WebTestClient
from Spring 5 reactive 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测试用例,它应该会通过。
- 使用springwebreactive的WebClient
我们也可以使用
WebClient
to call the 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 Web Flux以及如何构建helloworld反应式restfulweb服务。
很高兴看到像Spring这样的流行框架支持反应式编程模型。但是我们有很多内容需要讨论,因为如果您的所有依赖项都不是被动的和非阻塞的,那么您的应用程序也不是真正的被动的。
例如,关系数据库供应商没有反应式驱动程序,因为它们依赖于JDBC,这不是被动的。因此hibernateapi也是非反应性的。因此,如果您使用的是关系数据库,那么您还不能构建一个真正的反应式应用程序。我希望它迟早会改变。
参考文献:官方文件