跳到主要内容
版本:4.0.2

Reactive Web Applications

QWen Max 中英对照 Reactive Web Applications

Spring Boot 通过为 Spring WebFlux 提供自动配置,简化了响应式 Web 应用程序的开发。

“Spring WebFlux 框架”

Spring WebFlux 是 Spring Framework 5.0 中引入的全新响应式 Web 框架。与 Spring MVC 不同,它不需要 Servlet API,完全异步且非阻塞,并通过 Reactor 项目 实现了 Reactive Streams 规范。

Spring WebFlux 有两种风格:函数式和基于注解的。基于注解的方式与 Spring MVC 模型非常接近,如下例所示:

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/users")
public class MyRestController {

private final UserRepository userRepository;

private final CustomerRepository customerRepository;

public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}

@GetMapping("/{userId}")
public Mono<User> getUser(@PathVariable Long userId) {
return this.userRepository.findById(userId);
}

@GetMapping("/{userId}/customers")
public Flux<Customer> getUserCustomers(@PathVariable Long userId) {
return this.userRepository.findById(userId).flatMapMany(this.customerRepository::findByUser);
}

@DeleteMapping("/{userId}")
public Mono<Void> deleteUser(@PathVariable Long userId) {
return this.userRepository.deleteById(userId);
}

}

WebFlux 是 Spring Framework 的一部分,详细信息可在其参考文档中找到。

“WebFlux.fn” 是函数式变体,它将路由配置与请求的实际处理分离开来,如下例所示:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicate;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {

private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);

@Bean
public RouterFunction<ServerResponse> monoRouterFunction(MyUserHandler userHandler) {
return route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}

}
import reactor.core.publisher.Mono;

import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

@Component
public class MyUserHandler {

public Mono<ServerResponse> getUser(ServerRequest request) {
...
}

public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
...
}

public Mono<ServerResponse> deleteUser(ServerRequest request) {
...
}

}

“WebFlux.fn” 是 Spring Framework 的一部分,详细信息可在其参考文档中找到。

提示

你可以定义任意多个 RouterFunction Bean,以模块化的方式组织路由定义。如果需要应用优先级,可以对这些 Bean 进行排序。

首先,将 spring-boot-starter-webflux 模块添加到你的应用程序中。

备注

在你的应用中同时添加 spring-boot-starter-webspring-boot-starter-webflux 模块会导致 Spring Boot 自动配置 Spring MVC,而不是 WebFlux。之所以选择这种行为,是因为许多 Spring 开发者会在他们的 Spring MVC 应用中添加 spring-boot-starter-webflux,以便使用响应式的 WebClient。你仍然可以通过设置所选的应用类型为 SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE) 来强制指定使用 WebFlux。

Spring WebFlux 自动配置

Spring Boot 为 Spring WebFlux 提供了自动配置,适用于大多数应用程序。

自动配置在 Spring 默认配置的基础上增加了以下功能:

如果你想保留 Spring Boot WebFlux 的特性,并且希望添加额外的 WebFlux 配置,你可以添加自己的类型为 WebFluxConfigurer@Configuration 类,但不要使用 @EnableWebFlux

如果你想对自动配置的 HttpHandler 进行额外的自定义,可以定义类型为 WebHttpHandlerBuilderCustomizer 的 Bean,并使用它们来修改 WebHttpHandlerBuilder

如果你想完全控制 Spring WebFlux,可以添加你自己的使用 @EnableWebFlux 注解的 @Configuration

Spring WebFlux 转换服务

如果你想自定义 Spring WebFlux 所使用的 ConversionService,你可以提供一个带有 addFormatters 方法的 WebFluxConfigurer Bean。

也可以使用 spring.webflux.format.* 配置属性来自定义转换。当未进行配置时,将使用以下默认值:

属性DateTimeFormatter格式化类型
spring.webflux.format.dateofLocalizedDate(FormatStyle.SHORT)java.util.DateLocalDate
spring.webflux.format.timeofLocalizedTime(FormatStyle.SHORT)java.time 的 LocalTimeOffsetTime
spring.webflux.format.date-timeofLocalizedDateTime(FormatStyle.SHORT)java.time 的 LocalDateTimeOffsetDateTimeZonedDateTime

使用 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 编解码器

Spring WebFlux 使用 HttpMessageReaderHttpMessageWriter 接口来转换 HTTP 请求和响应。它们通过 CodecConfigurer 进行配置,通过检查 classpath 中可用的库来设置合理的默认值。

Spring Boot 为编解码器提供了专用的配置属性 spring.http.codecs.*。它还会通过使用 CodecCustomizer 实例进行进一步的自定义。例如,spring.jackson.* 配置项会被应用到 Jackson 编解码器上。

如果你需要添加或自定义编解码器,可以创建一个自定义的 CodecCustomizer 组件,如下例所示:

import org.springframework.boot.http.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerSentEventHttpMessageReader;

@Configuration(proxyBeanMethods = false)
public class MyCodecsConfiguration {

@Bean
public CodecCustomizer myCodecCustomizer() {
return (configurer) -> {
configurer.registerDefaults(false);
configurer.customCodecs().register(new ServerSentEventHttpMessageReader());
// ...
};
}

}

静态内容

默认情况下,Spring Boot 会从 classpath 中名为 /static(或 /public/resources/META-INF/resources)的目录提供静态内容。它使用 Spring WebFlux 中的 ResourceWebHandler,因此你可以通过添加自己的 WebFluxConfigurer 并重写 addResourceHandlers 方法来修改该行为。

默认情况下,资源被映射到 /**,但你可以通过设置 spring.webflux.static-path-pattern 属性来调整该映射。例如,将所有资源重定位到 /resources/** 可以按如下方式实现:

spring.webflux.static-path-pattern=/resources/**

你也可以通过使用 spring.web.resources.static-locations 来自定义静态资源的位置。这样做会用一组目录位置替换默认值。如果你这样做了,默认的欢迎页检测就会切换到你自定义的位置。因此,如果在启动时你的任意一个位置中存在 index.html,它就会成为应用程序的主页。

除了前面列出的“标准”静态资源位置外,Webjars 内容 有一个特殊处理。默认情况下,任何路径位于 /webjars/** 下的资源,如果以 Webjars 格式打包在 jar 文件中,则会从这些 jar 文件中提供。该路径可以通过 spring.webflux.webjars-path-pattern 属性进行自定义。

提示

Spring WebFlux 应用程序并不严格依赖于 Servlet API,因此无法打包为 war 文件,也不使用 src/main/webapp 目录。

欢迎页面

Spring Boot 支持静态和模板化的欢迎页。它首先在配置的静态内容位置中查找 index.html 文件。如果未找到,则会查找 index 模板。如果找到其中任意一个,就会自动将其用作应用程序的欢迎页。

这仅作为应用程序定义的实际索引路由的后备方案。其排序由 HandlerMapping Bean 的顺序决定,默认顺序如下:

org.springframework.web.reactive.function.server.support.RouterFunctionMapping使用 RouterFunction Bean 声明的端点
org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping@Controller Bean 中声明的端点
用于 Welcome Page 的 RouterFunctionMapping欢迎页支持

模板引擎

除了 REST Web 服务,你还可以使用 Spring WebFlux 来提供动态 HTML 内容。Spring WebFlux 支持多种模板技术,包括 Thymeleaf、FreeMarker 和 Mustache。

Spring Boot 为以下模板引擎提供了自动配置支持:

备注

并非所有 FreeMarker 特性都支持 WebFlux。更多细节,请查看每个属性的描述。

当你使用这些模板引擎之一并采用默认配置时,你的模板会自动从 src/main/resources/templates 目录中加载。

错误处理

Spring Boot 提供了一个 WebExceptionHandler,以合理的方式处理所有错误。它在处理顺序中的位置紧接在 WebFlux 提供的处理器之前,而 WebFlux 的处理器被视为最后的兜底处理。对于机器客户端,它会生成一个包含错误详情、HTTP 状态和异常消息的 JSON 响应;对于浏览器客户端,则提供一个“whitelabel”错误处理器,以 HTML 格式渲染相同的数据。你也可以提供自己的 HTML 模板来显示错误(参见下一节)。

在直接自定义 Spring Boot 中的错误处理之前,你可以利用 Spring WebFlux 中的 RFC 9457 Problem Details 支持。Spring WebFlux 可以生成 application/problem+json 媒体类型的自定义错误消息,例如:

{
"type": "https://example.org/problems/unknown-project",
"title": "Unknown project",
"status": 404,
"detail": "No project found for id 'spring-unknown'",
"instance": "/projects/spring-unknown"
}

可以通过将 spring.webflux.problemdetails.enabled 设置为 true 来启用此支持。

自定义此功能的第一步通常是使用现有机制,但替换或增强错误内容。为此,你可以添加一个类型为 ErrorAttributes 的 Bean。

要更改错误处理行为,你可以实现 ErrorWebExceptionHandler 并注册该类型的 Bean 定义。由于 ErrorWebExceptionHandler 属于较低层级的接口,Spring Boot 还提供了一个便捷的 AbstractErrorWebExceptionHandler,让你能够以 WebFlux 函数式的方式处理错误,如下例所示:

import reactor.core.publisher.Mono;

import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.webflux.autoconfigure.error.AbstractErrorWebExceptionHandler;
import org.springframework.boot.webflux.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder;

@Component
public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {

public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties webProperties,
ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer) {
super(errorAttributes, webProperties.getResources(), applicationContext);
setMessageReaders(serverCodecConfigurer.getReaders());
setMessageWriters(serverCodecConfigurer.getWriters());
}

@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml);
}

private boolean acceptsXml(ServerRequest request) {
return request.headers().accept().contains(MediaType.APPLICATION_XML);
}

public Mono<ServerResponse> handleErrorAsXml(ServerRequest request) {
BodyBuilder builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR);
// ... additional builder calls
return builder.build();
}

}

为了获得更完整的控制,你也可以直接继承 DefaultErrorWebExceptionHandler 并重写特定的方法。

在某些情况下,控制器层面处理的错误不会被 Web Observations 或 指标基础设施 记录。应用程序可以通过在 Observation Context 上设置已处理的异常,确保这些异常被 Observations 记录下来。

自定义错误页面

如果你想为某个特定的状态码显示自定义的 HTML 错误页面,可以添加从 error/* 解析的视图,例如通过在 /error 目录下添加文件来实现。错误页面既可以是静态 HTML(即放置在任意静态资源目录下),也可以使用模板构建。文件名应为精确的状态码、状态码系列掩码,或者在没有其他匹配项时使用 error 作为默认名称。请注意,默认错误视图的路径是 error/error,而在 Spring MVC 中,默认错误视图是 error

例如,要将 404 映射到一个静态 HTML 文件,你的目录结构如下所示:

src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>

要使用 Mustache 模板映射所有 5xx 错误,你的目录结构如下所示:

src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.mustache
+- <other templates>

Web Filters

Spring WebFlux 提供了一个 WebFilter 接口,可用于过滤 HTTP 请求-响应交换。在应用上下文中找到的 WebFilter Bean 将被自动用于过滤每次交换。

当过滤器的顺序很重要时,它们可以实现 Ordered 接口,或者使用 @Order 注解。Spring Boot 的自动配置可能会为你配置 Web 过滤器。当它这样做时,将使用下表中所示的顺序:

Web FilterOrder
WebFilterChainProxy (Spring Security)-100
HttpExchangesWebFilterOrdered.LOWEST_PRECEDENCE - 10

API 版本控制

Spring WebFlux 支持 API 版本控制,可用于随着时间推移逐步演进 HTTP API。同一个 @Controller 路径可以被多次映射,以支持不同版本的 API。

更多详情请参见 Spring Framework 的参考文档

一旦添加了映射,你还需要配置 Spring WebFlux,使其能够使用随请求发送的任何版本信息。通常,版本信息会通过 HTTP 头、查询参数或路径的一部分发送。

要配置 Spring WebFlux,你可以使用一个 WebFluxConfigurer bean 并重写 configureApiVersioning(…​) 方法,或者也可以使用属性进行配置。

例如,以下代码将使用 X-Version HTTP 头来获取版本信息,并在未发送该头时默认使用 1.0.0

spring.webflux.apiversion.default=1.0.0
spring.webflux.apiversion.use.header=X-Version

为了更完整的控制,你还可以定义 ApiVersionResolverApiVersionParserApiVersionDeprecationHandler Bean,它们将被注入到自动配置的 Spring MVC 配置中。

提示

API 版本控制在客户端也受支持,WebClientRestClient 均可使用。详见 API Versioning

嵌入式响应式服务器支持

Spring Boot 包含对以下嵌入式响应式 Web 服务器的支持:Reactor Netty、Tomcat 和 Jetty。大多数开发者使用相应的 starter 来获取一个完全配置好的实例。默认情况下,嵌入式服务器在端口 8080 上监听 HTTP 请求。

自定义 Reactive 服务器

常见的响应式 Web 服务器设置可以通过使用 Spring Environment 属性进行配置。通常,你会在 application.propertiesapplication.yaml 文件中定义这些属性。

常见的服务器设置包括:

  • 网络设置:用于监听传入 HTTP 请求的端口(server.port)、要绑定的接口地址(server.address)等。

  • 错误管理:错误页面的位置(spring.web.error.path)等。

  • SSL

  • HTTP 压缩

Spring Boot 尽可能多地暴露了通用设置,但这并不总是可行的。对于这些情况,专用的命名空间(如 server.netty.*)提供了针对服务器的特定自定义配置。

提示

完整列表请参见 ServerProperties 类。

编程式自定义

如果你需要以编程方式配置你的响应式 Web 服务器,可以注册一个实现 WebServerFactoryCustomizer 接口的 Spring Bean。WebServerFactoryCustomizer 提供了对 ConfigurableReactiveWebServerFactory 的访问,该工厂包含许多用于自定义的 setter 方法。以下示例展示了如何以编程方式设置端口:

import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.server.reactive.ConfigurableReactiveWebServerFactory;
import org.springframework.stereotype.Component;

@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {

@Override
public void customize(ConfigurableReactiveWebServerFactory server) {
server.setPort(9000);
}

}

JettyReactiveWebServerFactoryNettyReactiveWebServerFactoryTomcatReactiveWebServerFactoryConfigurableReactiveWebServerFactory 的专用变体,分别提供了针对 Jetty、Reactor Netty 和 Tomcat 的额外定制化 setter 方法。以下示例展示了如何定制 NettyReactiveWebServerFactory,以访问 Reactor Netty 特有的配置选项:

import java.time.Duration;

import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyNettyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {

@Override
public void customize(NettyReactiveWebServerFactory factory) {
factory.addServerCustomizers((server) -> server.idleTimeout(Duration.ofSeconds(20)));
}

}

直接自定义 ConfigurableReactiveWebServerFactory

对于需要从 ReactiveWebServerFactory 扩展的更高级用例,你可以自行暴露一个该类型的 Bean。

为许多配置选项提供了 Setter 方法。如果你需要执行一些更特殊的操作,还提供了一些受保护的方法“钩子”。详情请参见 ConfigurableReactiveWebServerFactory 的 API 文档。

备注

自动配置的定制器仍会应用于你自定义的工厂,因此请谨慎使用该选项。

Reactive Server Resources 配置

在自动配置 Reactor Netty 或 Jetty 服务器时,Spring Boot 将创建特定的 bean,为服务器实例提供 HTTP 资源:ReactorResourceFactoryJettyResourceFactory

默认情况下,这些资源也会与 Reactor Netty 和 Jetty 客户端共享,以实现最佳性能,前提是:

  • 服务器和客户端使用相同的技术

  • 客户端实例是使用 Spring Boot 自动配置的 WebClient.Builder Bean 构建的

开发者可以通过提供自定义的 ReactorResourceFactoryJettyResourceFactory Bean 来覆盖 Jetty 和 Reactor Netty 的资源配置——该配置将同时应用于客户端和服务器。

你可以在 WebClient Runtime 章节中了解更多关于客户端的资源配置信息。