# 8.2.1. “Spring WebFlux 框架”

Spring WebFlux 是 Spring Framework 5.0 中引入的新的响应式 Web 框架。与 Spring MVC 不同，它不需要 servlet API，完全异步且非阻塞，并通过[Reactor 项目实现](https://projectreactor.io/)[Reactive Streams](https://www.reactive-streams.org/)规范。

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

```java
@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 框架的一部分，详细信息可在其[参考文档](https://docs.spring.io/spring-framework/reference/6.1/web/webflux.html)中找到。

“WebFlux.fn”，功能变体，将路由配置与请求的实际处理分开，如以下示例所示：

```java
@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();
    }

}
```

```java
@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 框架的一部分，详细信息可在其[参考文档](https://docs.spring.io/spring-framework/reference/6.1/web/webflux-functional.html)中找到。

> 您可以定义任意数量的`RouterFunction`bean，以模块化路由器的定义。如果您需要应用优先级，可以对bean类排序。

首先，将该`spring-boot-starter-webflux`模块添加到您的应用程序中。

> 在应用程序中添加`spring-boot-starter-web`和`spring-boot-starter-webflux`模块会导致 Spring Boot 自动配置 Spring MVC，而不是 WebFlux。选择这种行为是因为许多 Spring 开发人员添加`spring-boot-starter-webflux`到他们的 Spring MVC 应用程序中以使用响应式`WebClient`. 您仍然可以通过将所选应用程序类型设置为 来强制执行您的选择`SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)`。

**Spring WebFlux 自动配置**

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

自动配置在 Spring 默认设置的基础上添加了以下功能：

* 为`HttpMessageReader`和`HttpMessageWriter`实例配置编解码器（[本文档稍后](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.reactive.webflux.httpcodecs)介绍）。
* 支持提供静态资源，包括支持 WebJars（[本文档稍后](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.static-content)介绍）。

如果您想保留 Spring Boot WebFlux 功能并添加额外的[WebFlux 配置](https://docs.spring.io/spring-framework/reference/6.1/web/webflux/config.html)，您可以添加自己的`@Configuration`type 类`WebFluxConfigurer`，但**不添加** `@EnableWebFlux`.

如果你想完全控制Spring WebFlux，你可以添加你自己的`@Configuration`注释`@EnableWebFlux`。

**Spring WebFlux 转换服务**

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

还可以使用`spring.webflux.format.*`配置属性来自定义转换。如果未配置，则使用以下默认值：

| 财产                                | `DateTimeFormatter`                      |
| --------------------------------- | ---------------------------------------- |
| `spring.webflux.format.date`      | `ofLocalizedDate(FormatStyle.SHORT)`     |
| `spring.webflux.format.time`      | `ofLocalizedTime(FormatStyle.SHORT)`     |
| `spring.webflux.format.date-time` | `ofLocalizedDateTime(FormatStyle.SHORT)` |

**具有 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 编解码器**

Spring WebFlux 使用`HttpMessageReader`和`HttpMessageWriter`接口来转换 HTTP 请求和响应。通过查看类路径中可用的库，将它们配置为具有合理的默认值的`CodecConfigurer`。

Spring Boot 为编解码器提供了专用的配置属性 `spring.codec.*`。它还通过使用 `CodecCustomizer` 实例来应用进一步的自定义。例如， `spring.jackson.*` 配置键应用于 Jackson 编解码器。

如果您需要添加或自定义编解码器，您可以创建自定义`CodecCustomizer`组件，如下例所示：

```java
@Configuration(proxyBeanMethods = false)
public class MyCodecsConfiguration {

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

}
```

您还可以利用[Boot 的自定义 JSON 序列化器和反序列化器](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.json.jackson.custom-serializers-and-deserializers)。

**静态内容**

默认情况下，Spring Boot 从类路径中名为`/static`(或 `/public`或 `/resources`或`/META-INF/resources` )的目录提供静态内容。它使用Spring WebFlux的`ResourceWebHandler`，以便您可以通过添加自己的`WebFluxConfigurer`并覆盖`addResourceHandlers`方法来修改该行为。

默认情况下，资源映射到`/**`，但您可以通过设置`spring.webflux.static-path-pattern`属性来调整它。例如，将所有资源重新定位`/resources/**`可以实现如下：

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

您还可以使用`spring.web.resources.static-locations` 自定义静态资源位置。这样做会用目录位置列表替换默认值。如果您这样做，默认欢迎页面检测将切换到您的自定义位置。因此，如果启动时在任何位置有`index.html`，则它是应用程序的主页。

除了前面列出的“标准”静态资源位置之外，还为[Webjars 内容](https://www.webjars.org/)制作了一个特殊情况。默认情况下，如果以 Webjars 格式打包，则任何路径为`/webjars/**` 的资源都将从 jar 文件提供。可以使用属性`spring.webflux.webjars-path-pattern`自定义路径。

> Spring WebFlux应用程序并不严格依赖于servlet API，因此它们不能部署为war文件并且不使用`src/main/webapp`目录。

**欢迎页面**

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

这仅充当应用程序定义的实际索引路由的后备。顺序由`HandlerMapping`bean 的顺序定义，默认情况如下：

| 映射bean                         | 备注                           |
| ------------------------------ | ---------------------------- |
| `RouterFunctionMapping`        | `RouterFunction`使用beans声明的端点 |
| `RequestMappingHandlerMapping` | `@Controller`bean中声明的端点      |
| `RouterFunctionMapping`欢迎页面    | 欢迎页面支持                       |

**模板引擎**

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

Spring Boot 包括对以下模板引擎的自动配置支持：

* [freemarker](https://freemarker.apache.org/docs/)
* [thymeleaf](https://www.thymeleaf.org/)
* [mustache](https://mustache.github.io/)

当您使用这些模板引擎之一和默认配置时，您的模板会自动从`src/main/resources/templates`加载.

**错误处理**

Spring Boot 提供了一种`WebExceptionHandler`以合理的方式处理所有错误的方法。它在处理顺序中的位置紧邻 WebFlux 提供的处理程序之前，这些处理程序被认为是最后的。对于机器客户端，它会生成一个 JSON 响应，其中包含错误、HTTP 状态和异常消息的详细信息。对于浏览器客户端，有一个“whitelabel”错误处理程序，可以以 HTML 格式呈现相同的数据。您还可以提供自己的 HTML 模板来显示错误（请参阅[下一节](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.reactive.webflux.error-handling.error-pages)）。

在直接在 Spring Boot 中自定义错误处理之前，您可以利用Spring WebFlux 中的[RFC 7807 Problem Details](https://docs.spring.io/spring-framework/reference/6.1/web/webflux/ann-rest-exceptions.html)支持。Spring WebFlux 可以使用`application/problem+json`媒体类型生成自定义错误消息，例如：

```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
@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`并重写特定方法。

[在某些情况下，度量基础设施](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.metrics.supported.spring-webflux)不会记录在控制器或处理函数级别处理的错误。应用程序可以通过将已处理的异常设置为请求属性来确保此类异常与请求指标一起记录：

```java
@Controller
public class MyExceptionHandlingController {

    @GetMapping("/profile")
    public Rendering userProfile() {
        // ...
        throw new IllegalStateException();
    }

    @ExceptionHandler(IllegalStateException.class)
    public Rendering handleIllegalState(ServerWebExchange exchange, IllegalStateException exc) {
        exchange.getAttributes().putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc);
        return Rendering.view("errorView").modelAttribute("message", exc.getMessage()).build();
    }

}
```

**自定义错误页面**

如果要显示给定状态代码的自定义 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>
```

**网页过滤器**

Spring WebFlux 提供了一个`WebFilter`可以实现过滤 HTTP 请求-响应交换的接口。 `WebFilter`在应用程序上下文中找到的 bean 将自动用于过滤每个交换。

如果过滤器的顺序很重要，则可以实现`Ordered`或注释`@Order`。Spring Boot 自动配置可以为您配置 Web 过滤器。执行此操作时，将使用下表中显示的顺序：

| 网页过滤器                       | 命令                               |
| --------------------------- | -------------------------------- |
| `WebFilterChainProxy`（春季安全） | `-100`                           |
| `HttpExchangesWebFilter`    | `Ordered.LOWEST_PRECEDENCE - 10` |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doc.shiker.tech/spring-boot-can-kao-wen-dang/8.-wang-luo/8.2-fan-ying-shi-wang-luo-ying-yong-cheng-xu/8.2.1.-spring-webflux-kuang-jia.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
