Spring WebFlux 响应式编程:构建高性能Web应用

这是文章《春季 WebFlux – 春季响应式编程》的第1部分(共3部分)。

Spring WebFlux 是 Spring 5 引入的新模块,标志着 Spring 框架迈向响应式编程模型的第一步。

响应式编程

响应式编程是一种以事件驱动和异步流处理为基础的编程范式,它能提供高效且灵活的编程体验。如果您对响应式编程模型尚不熟悉,强烈建议您阅读以下文章进行学习:

如果您是初次接触 Spring 5,那么请仔细阅读 Spring 5 的特点

Spring WebFlux 核心概念

Spring WebFlux 与 Spring MVC
    1. Mono:实现了 Publisher 接口,并返回 0 或 1 个元素。
  1. Flux:实现了 Publisher 接口,并返回 N 个元素。

Spring WebFlux Hello World 示例

Spring WebFlux 示例

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 示例</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/> <!-- 从仓库查找父级 -->
	</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 快照仓库</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring 里程碑仓库</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-snapshots</id>
			<name>Spring 快照插件仓库</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</pluginRepository>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring 里程碑插件仓库</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-webfluxspring-boot-starter-parent。其他依赖项主要用于创建 JUnit 测试用例。

Spring WebFlux 处理器

Spring WebFlux 处理器方法

Spring WebFlux 处理器方法用于处理请求,并以 MonoFlux 作为响应返回。

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 路由器方法

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 应用程序

现在,让我们使用 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 应用程序

Eclipse 运行为 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 应用程序测试

我们可以用多种方法测试我们的应用程序。

1. 使用CURL命令

$ curl https://localhost:8080/helloWorld
Hello World!
$

2. 在浏览器中打开URL

直接在浏览器中访问 https://localhost:8080/helloWorld

3. 使用Spring 5的WebTestClient进行单元测试

以下是一个使用Spring 5响应式 WebTestClient 测试REST Web服务的JUnit测试示例:

package com.Olivia.spring;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;

@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 (200)
                .expectBody(String.class).isEqualTo("Hello World!"); // 检查响应体类型和内容是否一致
    }
}

将其作为一个JUnit测试用例运行,应该会顺利通过。

4. 使用Spring Web Reactive的WebClient调用REST Web服务

WebClient 也可以用于调用REST Web服务,以下是一个示例:

package com.Olivia.spring.client;

import org.springframework.http.MediaType;
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<String> result = client.get()
                .uri("/helloWorld")
                .accept(MediaType.TEXT_PLAIN)
                .retrieve() // 替换 exchange(),更简洁地获取响应体
                .bodyToMono(String.class); // 将响应体转换为Mono<String>

        System.out.println("结果 = " + result.block());
    }
}

将其作为一个简单的Java应用程序运行,你应该能看到正确的输出和大量的调试消息。

总结

在这篇文章中,我们学习了Spring WebFlux以及如何构建一个“Hello World”响应式RESTful Web服务。看到像Spring这样的流行框架也支持响应式编程模型,真是令人高兴。但是我们还有很多需要处理,因为如果你的所有依赖都不是响应式且非阻塞的,那么你的应用程序也不是真正的响应式。例如,关系型数据库供应商并没有提供响应式的驱动程序,因为它们依赖于JDBC,而JDBC不是响应式的。因此,Hibernate API也是非响应式的。所以,如果你使用的是关系型数据库,那么你目前还无法构建一个真正的响应式应用程序。但我希望这将会很快改变。

你可以从我的GitHub仓库下载项目代码。

参考:

bannerAds