Spring ControllerAdvice - не удалось переопределить handleHttpRequestMethodNotSupported () в ResponseEntityExceptionHandler - PullRequest
2 голосов
/ 05 июня 2019

Вот несколько фактов для ситуации, с которой я сейчас сталкиваюсь

  1. Недавно я создал RestControllerAdvice с различными ExceptionHandler в качестве глобального обработчика исключений для моего Spring RestController.

  2. Поскольку я хотел бы вернуть свой настроенный ответ json для обработки предопределенной ошибки HTTP, как указано в ResponseEntityExceptionHandler, мой класс 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 ]

2 голосов
/ 18 июня 2019

Из ответа jackycflau мы можем суммировать 2 вопроса.

Q1.Почему удаление annotations=RestController.class будет работать для HttpRequestMethodNotSupportedException

Q2.Почему только HttpRequestMethodNotSupportedException не пойман?

Чтобы ответить на эти 2 вопроса, нам нужно взглянуть на код о том, как пружина обрабатывает исключения.Следующий исходный код основан на пружине 4.3.5.

Во время обработки запроса DispatcherServlet весной, при возникновении ошибки HandlerExceptionResolver попытается устранить исключение.В данном случае исключение делегируется ExceptionHandlerExceptionResolver.Метод для определения того, какой метод для устранения исключения (getExceptionHandlerMethod в ExceptionHandlerExceptionResolver.java строке 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());
}

Читая код, мы можем объяснить, чтопроисходит:

Ответ для Q1

Когда annotations=RestController.class удалено, hasSelectors вернет true, и, следовательно, isApplicableToBeanType также вернет true.Таким образом, HttpRequestMethodNotSupportedException будет обрабатываться TestRestExceptionHandler в этом случае.

Ответ для Q2

Для HttpRequestMethodNotSupportedException, DispatcherSerlvet не может найти метод контроллера для обработки запроса.Следовательно, handlerMethod, переданное getExceptionHandlerMethod, равно null, затем beanType, переданное isApplicableToBeanType, также равно нулю и возвращается false.

С другой стороны, DispatcherSerlvet может найти метод управления для HttpMessageNotReadableException или HttpMediaTypeNotSupportedException.Таким образом, метод обработчика контроллера остатка будет передан getExceptionHandlerMethod и isApplicableToBeanType вернет true.

1 голос
/ 06 июня 2019

Я обнаружил виновника проблемы, которая касается аннотации @RestControllerAdvice.

Обычно я пометил класс @RestControllerAdvice(annotations=RestController.class).

После того, как я удаляю пару ключ-значение annotations (т.е. просто аннотируем класс с помощью @RestControllerAdvice), HttpRequestMethodNotSupportedException теперь успешно перехвачен.

Это решение, которым я могу только поделиться. Я не понимаю основную причину, и такое поведение мне кажется довольно странным ... Возможно, потому что HttpRequestMethodNotSupportedException не контролируется @RestController ??? (просто дикое предположение). Я буду счастлив, если кто-то может дать полное объяснение такого поведения.

...