Spring ControllerAdvice-无法在ResponseEntityExceptionHandler中重写handleHttpRequestMethodNotSupported()

时间:2019-06-05 11:00:48

标签: spring api spring-mvc microservices spring-restcontroller

关于我当前面临的情况,有一些事实

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

  2. 由于我想返回用于处理ResponseEntityExceptionHandler中指定的预定义HTTP错误的自定义响应json,因此我的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处理。

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

2 个答案:

答案 0 :(得分:2)

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

  

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

     

第二季度。为什么只抓不到HttpRequestMethodNotSupportedException

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

在春季DispatcherServlet处理请求期间,发生错误时,HandlerExceptionResolver将尝试解决该异常。在给定的情况下,将异常委托给ExceptionHandlerExceptionResolver。确定解决异常的方法的方法是(getExceptionHandlerMethod行417中的ExceptionHandlerExceptionResolver.java

/**
 * 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());
}

通过阅读代码,我们可以解释发生了什么:

第一季度的答案

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

第二季度的答案

对于HttpRequestMethodNotSupportedExceptionDispatcherSerlvet找不到控制器方法来处理请求。因此,传递给handlerMethod的{​​{1}}是getExceptionHandlerMethod,然后传递给null的{​​{1}}也为null并返回false。

另一方面,beanType可以找到isApplicableToBeanTypeDispatcherSerlvet的控制器方法。因此,其余控制器处理程序方法将传递给HttpMessageNotReadableException,而HttpMediaTypeNotSupportedException将返回true。

答案 1 :(得分:1)

我发现了问题的根源,该问题与@RestControllerAdvice注释有关。

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

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

这是我只能分享的解决方案。我不了解其根本原因,这种行为对我来说似乎很奇怪……可能是因为HttpRequestMethodNotSupportedException不受@RestController的控制吗??? (只是一个疯狂的猜测)。如果有人可以对这种行为提供完整的解释,我将很高兴。