|
此版本仍在开发中,尚未视为稳定版。如需最新稳定版本,请使用 Spring Boot 4.0.4! |
响应式网络应用程序
Spring Boot 通过提供对 Spring Webflux 的自动配置来简化了响应式Web应用程序的开发。
"Spring WebFlux 框架"
Spring WebFlux 是 Spring Framework 5.0 引入的新一代响应式 web 框架。 不同于 Spring MVC,它不依赖于 servlet API,并且是完全异步和非阻塞的,通过 Reactive Streams 规范实现,使用了 Reactor 项目。
Spring WebFlux 以两种形式出现:函数式和注解驱动的。<br>注解驱动的形式与 Spring MVC 模型非常接近,如下例所示:
-
Java
-
Kotlin
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);
}
}
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
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {
@GetMapping("/{userId}")
fun getUser(@PathVariable userId: Long): Mono<User> {
return userRepository.findById(userId)
}
@GetMapping("/{userId}/customers")
fun getUserCustomers(@PathVariable userId: Long): Flux<Customer> {
return userRepository.findById(userId).flatMapMany { user: User ->
customerRepository.findByUser(user)
}
}
@DeleteMapping("/{userId}")
fun deleteUser(@PathVariable userId: Long): Mono<Void> {
return userRepository.deleteById(userId)
}
}
WebFlux 是 Spring Framework 的一部分,详细信息请参阅其参考文档。
“WebFlux.fn”,函数式变体,将路由配置与实际处理请求的逻辑分离开来,如下例所示:
-
Java
-
Kotlin
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 org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.RequestPredicates.DELETE
import org.springframework.web.reactive.function.server.RequestPredicates.GET
import org.springframework.web.reactive.function.server.RequestPredicates.accept
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(proxyBeanMethods = false)
class MyRoutingConfiguration {
@Bean
fun monoRouterFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
return RouterFunctions.route(
GET("/{user}").and(ACCEPT_JSON), userHandler::getUser).andRoute(
GET("/{user}/customers").and(ACCEPT_JSON), userHandler::getUserCustomers).andRoute(
DELETE("/{user}").and(ACCEPT_JSON), userHandler::deleteUser)
}
companion object {
private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
}
}
-
Java
-
Kotlin
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) {
...
}
}
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono
@Component
class MyUserHandler {
fun getUser(request: ServerRequest?): Mono<ServerResponse> {
...
}
fun getUserCustomers(request: ServerRequest?): Mono<ServerResponse> {
...
}
fun deleteUser(request: ServerRequest?): Mono<ServerResponse> {
...
}
}
“WebFlux.fn” 是 Spring 框架的一部分,详细信息可在其参考文档中找到。
您可以定义任意数量的 RouterFunction Bean,以模块化路由器的定义。
如果需要应用优先级,可以对 Bean 进行排序。 |
要开始,请将spring-boot-starter-webflux模块添加到您的应用程序中。
在您的应用程序中同时添加 spring-boot-starter-web 和 spring-boot-starter-webflux 模块会导致 Spring Boot 自动配置 Spring MVC,而不是 WebFlux。
之所以选择这种行为,是因为许多 Spring 开发者会在其 Spring MVC 应用程序中添加 spring-boot-starter-webflux,以使用响应式的 WebClient。
您仍然可以通过将所选的应用程序类型设置为 SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE) 来强制指定您的选择。 |
Spring WebFlux 自动配置
Spring Boot 为 Spring WebFlux 提供了自动配置,适用于大多数应用程序。
Spring框架自动配置在Spring默认设置的基础上添加了以下功能:
-
为
HttpMessageReader和HttpMessageWriter实例配置编解码器(本文档稍后部分将对此进行描述)。 -
支持静态资源的提供,包括对WebJars的支持(将在本文件稍后部分 详细介绍)。
如果您想保留 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.*配置属性来自定义。
如果没有进行配置,将使用以下默认设置:
| <property> </property> | DateTimeFormatter |
格式 |
|---|---|---|
|
|
|
|
|
java.time 的 |
|
|
java.time 的 |
使用 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 编解码器
Spring WebFlux 使用 HttpMessageReader 和 HttpMessageWriter 接口来转换 HTTP 请求和响应。
它们通过 CodecConfigurer 进行配置,会根据类路径中可用的库提供合理的默认值。
Spring Boot 为编解码器提供了专用的配置属性,spring.http.codecs.*。
它还通过使用 CodecCustomizer 实例来应用进一步的自定义。
例如,spring.jackson.* 配置键会应用于 Jackson 编解码器。
如果您需要添加或自定义编解码器,可以创建一个自定义的 CodecCustomizer 组件,如下例所示:
-
Java
-
Kotlin
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());
// ...
};
}
}
import org.springframework.boot.http.codec.CodecCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.http.codec.CodecConfigurer
import org.springframework.http.codec.ServerSentEventHttpMessageReader
class MyCodecsConfiguration {
@Bean
fun myCodecCustomizer(): CodecCustomizer {
return CodecCustomizer { configurer: CodecConfigurer ->
configurer.registerDefaults(false)
configurer.customCodecs().register(ServerSentEventHttpMessageReader())
}
}
}
你也可以利用Boot 的自定义 JSON 序列化器和反序列化器。
静态内容
默认情况下,Spring Boot 会从类路径下名为 /static(或 /public、/resources 或 /META-INF/resources)的目录提供静态内容。
它使用了来自 Spring WebFlux 的 ResourceWebHandler,因此您可以通过添加自己的 WebFluxConfigurer 并重写 addResourceHandlers 方法来修改该行为。
默认情况下,资源映射在/**上,但可以通过设置spring.webflux.static-path-pattern属性来调整。
例如,将所有资源重新定位到/resources/**可以这样实现:
-
Properties
-
YAML
spring.webflux.static-path-pattern=/resources/**
spring:
webflux:
static-path-pattern: "/resources/**"
您也可以通过使用spring.web.resources.static-locations来自定义静态资源的位置。
这样做会用目录位置列表替换默认值。
如果这样做,应用程序的默认欢迎页面检测将切换到您的自定义位置。
因此,在启动时,如果您任何位置中有index.html文件,则该文件将成为应用的首页。
除了前面列出的“标准”静态资源位置外,Webjars 内容被视为一种特殊情况。
默认情况下,路径位于 /webjars/** 下的所有资源,如果它们是以 Webjars 格式打包的,则会从 jar 文件中提供。
该路径可以通过 spring.webflux.webjars-path-pattern 属性进行自定义。
Spring WebFlux 应用程序并不严格依赖于 servlet API,因此它们不能作为 war 文件部署,并且不使用 src/main/webapp 目录。 |
欢迎页面
Spring Boot 支持静态和模板欢迎页面。
它首先在配置的静态内容位置查找 index.html 文件。
如果没有找到,则查找 index 模板。
如果找到了其中之一,它会自动用作应用程序的欢迎页面。
这仅作为应用程序定义的实际索引路由的后备方案。
排序由 HandlerMapping Bean 的顺序决定,默认顺序如下:
|
使用 |
|
在 |
|
欢迎页面支持 |
模板引擎
除了REST web服务,您还可以使用Spring WebFlux 提供动态HTML内容。 Spring WebFlux 支持多种模板技术,包括Thymeleaf、FreeMarker和Mustache。
Spring Boot 包含以下模板引擎的支持:
| 不是所有FreeMarker功能都支持WebFlux。 请参阅每个属性的描述以获取更多详细信息。 |
当您使用这些模板引擎并以默认配置使用时,您的模板会自动从src/main/resources/templates中被识别。
错误处理
Spring Boot 提供了一个 WebExceptionHandler,以合理的方式处理所有错误。
它在处理顺序中的位置紧邻 WebFlux 提供的处理器之前,后者被视为最后执行。
对于机器客户端,它会生成一个包含错误详情、HTTP 状态码和异常消息的 JSON 响应。
对于浏览器客户端,有一个
在直接自定义 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 函数式方式处理错误,如下例所示:
-
Java
-
Kotlin
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();
}
}
import org.springframework.boot.autoconfigure.web.WebProperties
import org.springframework.boot.webflux.error.ErrorAttributes
import org.springframework.boot.webflux.autoconfigure.error.AbstractErrorWebExceptionHandler
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 reactor.core.publisher.Mono
@Component
class MyErrorWebExceptionHandler(
errorAttributes: ErrorAttributes, webProperties: WebProperties,
applicationContext: ApplicationContext, serverCodecConfigurer: ServerCodecConfigurer
) : AbstractErrorWebExceptionHandler(errorAttributes, webProperties.resources, applicationContext) {
init {
setMessageReaders(serverCodecConfigurer.readers)
setMessageWriters(serverCodecConfigurer.writers)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml)
}
private fun acceptsXml(request: ServerRequest): Boolean {
return request.headers().accept().contains(MediaType.APPLICATION_XML)
}
fun handleErrorAsXml(request: ServerRequest): Mono<ServerResponse> {
val builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
// ... additional builder calls
return builder.build()
}
}
为了获得更完整的视图,您也可以直接子类化 DefaultErrorWebExceptionHandler 并重写特定方法。
在某些情况下,控制器层面处理的错误不会被 Web 观测(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 过滤器
| Web 过滤器 | 订单 |
|---|---|
|
|
|
API 版本控制
Spring WebFlux 支持 API 版本控制,可用于随着时间推移逐步演进 HTTP API。
同一个 @Controller 路径可以被多次映射,以支持不同版本的 API。
更多详情请参阅Spring Framework 参考文档。
一旦添加了映射,您还需要配置 Spring WebFlux,使其能够使用随请求发送的任何版本信息。 通常,版本信息会通过 HTTP 头、查询参数或作为路径的一部分进行传递。
要配置 Spring WebFlux,您可以使用 WebFluxConfigurer Bean 并重写 configureApiVersioning(…) 方法,或者也可以使用属性进行配置。
例如,以下代码将使用 X-Version HTTP 头来获取版本信息,并在未发送该头时默认使用 1.0.0。
-
Properties
-
YAML
spring.webflux.apiversion.default=1.0.0
spring.webflux.apiversion.use.header=X-Version
spring:
webflux:
apiversion:
default: 1.0.0
use:
header: X-Version
为了获得更完整的控制,您还可以定义 ApiVersionResolver、ApiVersionParser 和 ApiVersionDeprecationHandler Bean,它们将被注入到自动配置的 Spring MVC 配置中。
客户端也支持 API 版本控制,WebClient 和 RestClient 均可使用。
详见API 版本控制。 |
嵌入式响应式服务器支持
Spring Boot 支持以下嵌入式响应式 Web 服务器:Reactor Netty、Tomcat 和 Jetty。 大多数开发者使用相应的 starter 来获取一个完全配置好的实例。 默认情况下,嵌入式服务器在 8080 端口监听 HTTP 请求。
自定义响应式服务器
常见的响应式 Web 服务器设置可以通过使用 Spring Environment 属性进行配置。
通常,您会在 application.properties 或 application.yaml 文件中定义这些属性。
常见的服务器设置包括:
Spring Boot 尽可能地暴露常见的设置,但这并不总是可行。
对于这些情况,专用命名空间如 server.netty.* 提供了服务器特定的自定义配置。
请参阅 ServerProperties 类以获取完整列表。 |
编程式自定义
如果您需要以编程方式配置响应式 Web 服务器,可以注册一个实现 WebServerFactoryCustomizer 接口的 Spring Bean。
WebServerFactoryCustomizer 提供了对 ConfigurableReactiveWebServerFactory 的访问,其中包含大量的自定义设置方法。
以下示例展示了如何以编程方式设置端口:
-
Java
-
Kotlin
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);
}
}
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.boot.web.server.reactive.ConfigurableReactiveWebServerFactory
import org.springframework.stereotype.Component
@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {
override fun customize(server: ConfigurableReactiveWebServerFactory) {
server.setPort(9000)
}
}
JettyReactiveWebServerFactory、NettyReactiveWebServerFactory 和 TomcatReactiveWebServerFactory 是 ConfigurableReactiveWebServerFactory 的专用变体,分别提供了针对 Jetty、Reactor Netty 和 Tomcat 的额外自定义设置方法。
以下示例展示了如何自定义 NettyReactiveWebServerFactory,以访问 Reactor Netty 特定的配置选项:
-
Java
-
Kotlin
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)));
}
}
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory
import org.springframework.stereotype.Component
import java.time.Duration
@Component
class MyNettyWebServerFactoryCustomizer : WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
override fun customize(factory: NettyReactiveWebServerFactory) {
factory.addServerCustomizers({ server -> server.idleTimeout(Duration.ofSeconds(20)) })
}
}
直接自定义 ConfigurableReactiveWebServerFactory
对于需要您从 ReactiveWebServerFactory 扩展的更高级用例,您可以自行公开该类型的 Bean。
为许多配置选项提供了 Setter 方法。
如果您需要执行更特殊的操作,还提供了几种受保护的方法“钩子”。
详细信息请参阅 ConfigurableReactiveWebServerFactory API 文档。
| 自配置的定制器仍然会应用到您的自定义工厂上,因此请谨慎使用此选项。 |
响应式服务器资源配置
当自动配置 Reactor Netty 或 Jetty 服务器时,Spring Boot 将创建特定的 Bean,为服务器实例提供 HTTP 资源:ReactorResourceFactory 或 JettyResourceFactory。
默认情况下,这些资源也会与Reactor Netty和Jetty客户端共享以获得最佳性能,前提是:
-
同一种技术用于服务器和客户端
-
客户端实例是使用由 Spring Boot 自动配置的
WebClient.BuilderBean 构建的
开发人员可以通过提供自定义的 ReactorResourceFactory 或 JettyResourceFactory Bean 来覆盖 Jetty 和 Reactor Netty 的资源配置 - 这将同时应用于客户端和服务器端。
您可以在WebClient 运行时 部分了解客户端方面的资源配置。