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