错误响应
REST服务的一个常见要求是在错误响应的body中包含详细信息。Spring框架支持“HTTP API的问题详情”规范,RFC 9457。
以下是对此支持的主要抽象概念:
-
ProblemDetail— 用于表示 RFC 9457 中的问题详情;这是一个简单的容器,既可以容纳规范中定义的标准字段,也可以容纳非标准字段。 -
ErrorResponse— 用于暴露 HTTP 错误响应细节的契约,包括 HTTP 状态码、响应头部以及符合 RFC 9457 格式的响应体;这允许异常封装并展示它们如何映射到 HTTP 响应的详细信息。所有 Spring WebFlux 异常都实现了这一契约。 -
ErrorResponseException— 基本的ErrorResponse实现类,其他类可以将其用作方便的基类。 -
ResponseEntityExceptionHandler— 一个方便的基类,用于处理所有 Spring WebFlux 异常以及任何ErrorResponseException,并渲染带有响应体的错误响应。
渲染
你可以在任何@ExceptionHandler或任何@RequestMapping方法中返回ProblemDetail或ErrorResponse,以渲染符合RFC 9457标准的响应。其处理流程如下:
ProblemDetail的status属性决定了 HTTP 状态码。- 如果尚未设置,
ProblemDetail的instance属性将根据当前 URL 路径进行设置。 - 在内容协商方面,当渲染
ProblemDetail时,Jackson 的HttpMessageConverter更倾向于使用 “application/problem+json” 而不是 “application/json”;如果没有找到兼容的媒体类型,也会退而使用 “application/problem+json”。
为了使Spring WebFlux异常以及任何ErrorResponseException能够返回RFC 9457格式的响应,需要继承ResponseEntityExceptionHandler,并在Spring配置中将其声明为@ControllerAdvice。该处理器包含一个@ExceptionHandler方法,用于处理任何ErrorResponse异常(包括所有内置的Web异常)。你可以添加更多的异常处理方法,并使用一个受保护的方法将任何异常映射到ProblemDetail对象上。
你可以通过 WebFlux 配置 使用 WebFluxConfigurer 注册 ErrorResponse 拦截器。利用它来拦截任何符合 RFC 9457 标准的响应,并执行相应的操作。
非标准字段
您可以通过以下两种方式之一,使用非标准字段来扩展RFC 9457响应。
首先,将数据插入到ProblemDetail的“properties”Map中。在使用Jackson库时,Spring框架会注册ProblemDetailJacksonMixin,该插件会确保这个“properties”Map被解包并在响应中作为顶级JSON属性呈现;同样,在反序列化过程中,任何未知属性也会被插入到这个Map中。
你也可以扩展ProblemDetail以添加专用的非标准属性。ProblemDetail中的拷贝构造函数使得子类能够轻松地从现有的ProblemDetail创建新的实例。例如,可以在一个@ControllerAdvice(如ResponseEntityExceptionHandler)中集中实现这一功能,该控制器Advice会将异常的ProblemDetail重新创建为包含额外非标准字段的子类实例。
在Spring Boot中,spring.webflux.problemdetails.enabled属性会自动配置一个ResponseEntityExceptionHandler来处理带有问题详情的内置异常。在这种情况下,如果你想接管对特定内置异常的处理,你可能更倾向于创建另一个@ControllerAdvice而不是继承ResponseEntityExceptionHandler。你需要确保你的处理器在Spring Boot配置的处理器之前执行,因为Spring Boot默认配置的处理器顺序是0。
定制化和国际化
自定义和国际化错误响应细节是一个常见的需求。对于Spring WebFlux异常,定制问题详情也是良好的实践,这样可以避免泄露实现细节。本节将描述对此的支持。
ErrorResponse会暴露“type”、“title”和“detail”字段的消息代码,以及“detail”字段的消息代码参数。ResponseEntityExceptionHandler通过MessageSource来解析这些信息,并据此更新相应的ProblemDetail字段。
消息代码的默认策略遵循以下模式:
problemDetail.[type|title|detail].[fully qualified exception class name]
ErrorResponse 可能会暴露多个消息代码,通常是在默认消息代码后添加一个后缀。下表列出了 Spring WebFlux 异常的消息代码及其相关参数:
| 异常类型 | 消息代码 | 消息代码参数 |
|---|---|---|
HandlerMethodValidationException | (默认) | {0} 列出所有验证错误。每个错误的消息代码和参数也通过 MessageSource 进行解析。 |
MethodNotAllowedException | (默认) | {0} 当前的 HTTP 方法, {1} 支持的 HTTP 方法列表 |
MissingRequestValueException | (默认) | {0} 值的标签(例如,“request header”、“cookie value”等), {1} 值的名称 |
NotAcceptableStatusException | (默认) | {0} 支持的媒体类型列表 |
NotAcceptableStatusException | (默认) + “.parseError” | |
ServerErrorException | (默认) | {0} 提供给类构造函数的失败原因 |
UnsupportedMediaTypeStatusException | (默认) | {0} 不支持的媒体类型, {1} 支持的媒体类型列表 |
UnsupportedMediaTypeStatusException | (默认) + “.parseError” | |
UnsatisfiedRequestParameterException | (默认) | {0} 参数条件列表 |
WebExchangeBindException | (默认) | {0} 全局错误列表, {1} 字段错误列表。每个错误的消息代码和参数也通过 MessageSource 进行解析。 |
NoResourceFoundException | (默认) | {0} 用于查找资源的请求路径(或其部分) |
与其他异常不同,WebExchangeBindException 和 HandlerMethodValidationException 的错误信息参数是基于一个 MessageSourceResolvable 错误列表的,这些错误也可以通过 MessageSource 资源包进行自定义。有关更多详细信息,请参阅 自定义验证错误。
客户端处理
当使用WebClient时,客户端应用程序可能会捕获WebClientResponseException;而当使用RestTemplate时,则可能会捕获RestClientResponseException。这些异常类提供了getResponseBodyAs方法,可以用来将错误响应体解码为任何目标类型,例如ProblemDetail或ProblemDetail的子类。