Spring ControllerAdvice - Fail to override handleHttpRequest



以下是我目前面临的情况的一些事实

  1. 我最近构建了一个具有各种ExceptionHandlerRestControllerAdvice作为我的 Spring RestController 的全局异常处理程序。

  2. 由于我想返回我的自定义响应 json 来处理ResponseEntityExceptionHandler中指定的预定义 HTTP 错误,我的RestControllerAdvice类继承了ResponseEntityExceptionHandler和方法,如handleHttpRequestMethodNotSupported()handleHttpMessageNotReadable()被覆盖。

  3. 我已经成功地覆盖了handleHttpMediaTypeNotSupported()handleHttpMessageNotReadable()但是当涉及到handleHttpRequestMethodNotSupported()时,我未能做到这一点。

这是我的代码摘录:

@Order(Ordered.HIGHEST_PRECEDENCE)
@RestControllerAdvice(annotations=RestController.class)
public class TestRestExceptionHandler extends ResponseEntityExceptionHandler{
@Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Request Method Not Supported");
return handleExceptionInternal(ex, response, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Message Not Readable");
return handleExceptionInternal(ex, response, headers, status, request);
}
@Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Media Type Not Supported");
return handleExceptionInternal(ex, response, headers, status, request);
}
}

handleHttpRequestMethodNotSupported()日志如下所示:

[2019-06-05T17:49:50.368+0800][XNIO-74 task-7][WARN ][o.s.w.s.m.s.DefaultHandlerExceptionResolver] Resolved exception caused by Handler execution: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported

handleHttpMessageNotReadable()日志如下所示:

[2019-06-05T17:50:21.915+0800][XNIO-74 task-8][WARN ][o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver] Resolved exception caused by Handler execution

如您所见,成功的代码由ExceptionHandlerExceptionResolver处理,而故障代码由DefaultHandlerExceptionResolver处理。

我想知道根本原因是什么,如果有人可以推荐任何可用的解决方案,我将不胜感激。谢谢。

jackycflau的答案中,我们可以总结为2个问题。

问题 1.为什么删除annotations=RestController.class适用于 HttpRequestMethodNotSupportedException

问题 2.为什么只有HttpRequestMethodNotSupportedException没有被抓住?

要回答这两个问题,我们需要查看有关 spring 如何处理异常的代码。以下源代码基于 spring 4.3.5。

在 SpringDispatcherServlet处理请求期间,当发生错误时,HandlerExceptionResolver将尝试解决异常。在给定的情况下,例外将委派给ExceptionHandlerExceptionResolver。确定解决异常的方法的方法ExceptionHandlerExceptionResolver.java(getExceptionHandlerMethod第 417 行中)

/**
* Find an {@code @ExceptionHandler} method for the given exception. The default
* implementation searches methods in the class hierarchy of the controller first
* and if not found, it continues searching for additional {@code @ExceptionHandler}
* methods assuming some {@linkplain ControllerAdvice @ControllerAdvice}
* Spring-managed beans were detected.
* @param handlerMethod the method where the exception was raised (may be {@code null})
* @param exception the raised exception
* @return a method to handle the exception, or {@code null}
*/
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
if (handlerMethod != null) {
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
}
for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
}
}
}
return null;
}

由于我们正在使用@RestControllerAdvice,我们只需要关注 for 循环,这决定了使用哪个ControllerAdviceBean。我们可以看到,isApplicableToBeanType的方法将判断ControllerAdviceBean是否适用,相关代码分别为(ControllerAdviceBean.java第 149 行)

/**
* Check whether the given bean type should be assisted by this
* {@code @ControllerAdvice} instance.
* @param beanType the type of the bean to check
* @see org.springframework.web.bind.annotation.ControllerAdvice
* @since 4.0
*/
public boolean isApplicableToBeanType(Class<?> beanType) {
if (!hasSelectors()) {
return true;
}
else if (beanType != null) {
for (String basePackage : this.basePackages) {
if (beanType.getName().startsWith(basePackage)) {
return true;
}
}
for (Class<?> clazz : this.assignableTypes) {
if (ClassUtils.isAssignable(clazz, beanType)) {
return true;
}
}
for (Class<? extends Annotation> annotationClass : this.annotations) {
if (AnnotationUtils.findAnnotation(beanType, annotationClass) != null) {
return true;
}
}
}
return false;
}
private boolean hasSelectors() {
return (!this.basePackages.isEmpty() || !this.assignableTypes.isEmpty() || !this.annotations.isEmpty());
}

通过阅读代码,我们可以解释正在发生的事情:

问题 1 的答案

删除annotations=RestController.class时,hasSelectors将返回 false,因此isApplicableToBeanType将返回 true。因此,在这种情况下,HttpRequestMethodNotSupportedException将由TestRestExceptionHandler处理。

问题 2 的答案

对于HttpRequestMethodNotSupportedExceptionDispatcherSerlvet找不到控制器方法来处理请求。因此handlerMethod传递给getExceptionHandlerMethodnull,然后传递给isApplicableToBeanTypebeanType也是空的,并且返回假。

另一方面,DispatcherSerlvet可以找到HttpMessageNotReadableExceptionHttpMediaTypeNotSupportedException的控制器方法。因此,其余控制器处理程序方法将传递给getExceptionHandlerMethod并且isApplicableToBeanType将返回 true。

我已经找到了问题的罪魁祸首,这是关于@RestControllerAdvice注释的。

最初,我用@RestControllerAdvice(annotations=RestController.class)注释了该类。

在我删除annotations键值对后(即只需用@RestControllerAdvice注释类),HttpRequestMethodNotSupportedException现在已成功捕获。

这是我只能分享的解决方案。我不明白根本原因,这种行为对我来说似乎很奇怪......可能是因为HttpRequestMethodNotSupportedException不受@RestController???的控制(只是一个疯狂的猜测)。如果有人能对这种行为给出完整的解释,我会很高兴。

最新更新