Spring Web 6 引入了对“HTTP API 的问题详细信息”规格,RFC 7807 https://www.rfc-editor.org/rfc/rfc7807.html.
至此,响应状态异常 https://docs.spring.io/spring-framework/docs/6.0.x/javadoc-api/org/springframework/web/server/ResponseStatusException.html现在实施错误响应 https://docs.spring.io/spring-framework/docs/6.0.x/javadoc-api/org/springframework/web/ErrorResponse.html接口并扩展了错误响应异常 https://docs.spring.io/spring-framework/docs/6.0.x/javadoc-api/org/springframework/web/ErrorResponseException.html class.
快速浏览一下 javadocs,我们可以看到所有这些都由 RFC 7807 格式支持问题详情 https://docs.spring.io/spring-framework/docs/6.0.x/javadoc-api/org/springframework/http/ProblemDetail.htmlbody,正如您可以想象的那样,它具有您收到的新响应的字段,并且还使用application/problem+json
响应中的媒体类型。
Here https://docs.spring.io/spring-framework/docs/6.0.x/reference/html/web.html#mvc-ann-rest-exceptions是对Spring 现在如何处理错误响应,这自然会朝着全面使用问题详细信息规范的方向发展.
现在,正常情况下,如果您只是依赖引导的错误处理机制而不进行任何进一步的更改,您仍然会看到与以前相同的响应。我的猜测是你正在使用@ControllerAdvice
延伸ResponseEntityExceptionHandler
。这样,您就可以启用 RFC 7807(根据 Spring 文档here https://docs.spring.io/spring-framework/docs/6.0.x/reference/html/web.html#mvc-ann-rest-exceptions-render)
所以,这就是为什么你的ResponseStatusException
已更改其正文内容。
配置问题详细信息响应正文以包含以前的字段
如果您需要坚持使用预先存在的字段(至少在您完全迁移到基于问题详细信息的方法之前),或者如果您只是想将自定义字段添加到错误响应中,则可以覆盖createResponseEntity
方法中的@ControlAdvice
类扩展ResponseEntityExceptionHandler
如下:
@ControllerAdvice
public class CustomExceptionsHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> createResponseEntity(@Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
if (body instanceof ProblemDetail) {
ProblemDetail problemDetail = ((ProblemDetail) body);
problemDetail.setProperty("error", problemDetail.getTitle());
problemDetail.setProperty("timestamp", new Date());
if (request instanceof ServletWebRequest) {
problemDetail.setProperty("path", ((ServletWebRequest) request).getRequest()
.getRequestURI());
}
}
return new ResponseEntity<>(body, headers, statusCode);
}
}
注意:我正在使用new Date()
而不是 Java Time 只是因为这就是 BootsDefaultErrorAttributes
类用途。另外,我没有使用 BootsErrorAttributes
为了简单起见。
请注意,定义path
字段有点棘手,因为problemDetail.getInstance()
回报null
在这个阶段;该框架稍后将其设置为HttpEntityMethodProcessor
.
当然,这个解决方案适用于 servlet 堆栈,但它也应该有助于弄清楚如何在反应式堆栈中继续进行。
这样,响应将如下所示:
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"instance": "/foo/99",
"error": "Not Found",
"path": "/foo/99",
"timestamp": "2023-01-06T10:00:20.509+00:00"
}
当然,它有重复的字段。如果您愿意,可以完全替换方法中的响应正文。
最后,这里有一个重要的建议:你必须有意识地实现这个逻辑;错误响应不应该用作调试工具(至少不应该在生产环境中 - 这就是 Boot 默认情况下禁用公开错误信息的原因),因为它可能会产生安全隐患(泄漏有关实现内部的信息,这可能是可能被利用)。
正如 RFC 7807 规范所指出的,它应该被用作“公开有关 HTTP 接口本身的更多细节的一种方式”。
自定义问题详细信息响应,并显示引发的异常
正如我们所理解的,通过上面使用的机制,我们无法访问原始的Exception
,有时可能需要使用指定的问题详细信息格式检索合适的结果。
这是一个示例,说明如何使用ResponseEntityExceptionHandler
实现这一目标的能力。例如,在本例中,我添加了一个额外的message
包含错误消息和errors
包含输入验证错误的字段:
@ControllerAdvice
public class CustomExceptionsHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
ResponseEntity<Object> response = super.handleExceptionInternal(ex, body, headers, statusCode, request);
if (response.getBody() instanceof ProblemDetail problemDetailBody) {
problemDetailBody.setProperty("message", ex.getMessage());
if (ex instanceof MethodArgumentNotValidException subEx) {
BindingResult result = subEx.getBindingResult();
problemDetailBody.setProperty("message", "Validation failed for object='" + result.getObjectName() + "'. " + "Error count: " + result.getErrorCount());
problemDetailBody.setProperty("errors", result.getAllErrors());
}
}
return response;
}
}
将 Boot 配置为也使用问题详细信息规范
Boot 尚未提供在其详细信息中使用问题详细信息规范的支持错误处理 https://docs.spring.io/spring-boot/docs/3.0.x/reference/htmlsingle/#web.servlet.spring-mvc.error-handling机制 - 在某些时候,它会,如此所示issue https://github.com/spring-projects/spring-boot/issues/19525.
但是,我们可以轻松地使用应用程序属性来指示,以启用 Spring MVC 提供的支持,以使用问题详细信息规范格式来处理它引发的异常(与我们包含ResponseEntityExceptionHandler
ControllerAdvice
):
spring.mvc.problemdetails.enabled=true
需要明确的是,这并没有实现 Spring MVC 框架未引发的其他异常的规范;对于那些,启动错误处理机制仍将检索其旧的ErrorAttributes
格式,对不同类型的错误产生不一致的响应。
将 @ExceptionHandler 响应委托给 Boot 错误处理模型
实际上,严格来说这并不是 Boot 3 的更改,但值得在现阶段提出。
当我们实施一个@ExceptionHandler
逻辑上,它将完全控制我们检索的响应。 Boot 3 确实允许轻松检索 ProblemDetail 格式的响应(通过检索ErrorResponse
, ProblemDetail
或其实现类就像现在的ResponseStatusException
),但目前还不清楚如何操作响应并且仍然依赖传统的引导错误处理逻辑。
现在,如果我们了解它是如何工作的,并意识到 Boot 确实为其准备了错误处理信息/error
到达之前的终点@ExceptionHandler
逻辑,那么我们可以用它来驱动对此机制的响应:
@ExceptionHandler({ EntityNotFoundException.class })
public ModelAndView resolveException(HttpServletRequest request, Exception ex) {
request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpStatus.BAD_REQUEST.value());
request.setAttribute(RequestDispatcher.ERROR_MESSAGE,
"This will override the error message configured by Boot");
ModelAndView mav = new ModelAndView();
mav.setViewName("/error");
return mav;
}
注意:如果我们不声明这一点@ExceptionHandler
在一个@RestController
,那么我们可以简单地检索/error
字符串而不必创建ModelAndView
实例。