Spring boot Rest отвечает пустым телом для исключений, отличных от тех, которые переопределены в моем @ControllerAdvice

У меня есть @ControllerAdvice, расширяющий ResponseEntityExceptionHandler в качестве попытки для меня контролировать стандартный ответ для любого исключения, вызванного в рабочем процессе вызова API.

Без совета Контроллера. Я получаю общий ответ на основе HTML, сгенерированный spring с правильными заголовками ответа. Но когда я добавляю свой @ControllerAdvice, Spring не отвечает общим телом ошибки. Тело пустое с правильными заголовками ответа

@Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
        HttpHeaders headers, HttpStatus status, WebRequest request) {

        String erroMessage = "Required Parameter: '"+ex.getParameterName()+"' was not available in the request.";
        TrsApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, erroMessage, ex, ApiErrorCode.INVALID_REQUEST);
        return buildResponseEntity(apiError);
}

Итак, теперь, если в запросе отсутствует обязательный параметр, поток красиво запускает мою переопределенную реализацию и отвечает полезной нагрузкой JSON с описанием ошибки. Но в случае любого другого исключения, такого как HttpMediaTypeNotAcceptableException, spring отвечает пустым телом.

Прежде чем я добавил свой совет, spring отвечал общим ответом об ошибке. Я новичок в экосистеме весенней загрузки. Нужна помощь в понимании того, является ли это ожидаемым поведением, или существует ли лучший подход для достижения централизованной обработки ошибок.


person Gagandeep Singh    schedule 10.06.2019    source источник


Ответы (3)


Думаю, я нашел решение для проглоченного тела, когда класс ControllerAdvice расширяется ResponeEntityExceptionHandler. В моем случае настройка выглядит так:

@ControllerAdvice
@Slf4j
class GlobalExceptionHandlers extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
                                      MethodArgumentNotValidException exception,
                                      HttpHeaders headers,
                                      HttpStatus status,
                                      WebRequest request) {
        // logic that creates apiError object (object with status, message, errorCode, etc)
        //...
        return handleExceptionInternal(exception, apiError, headers, status, request);
    }

И это прекрасно сработало для исключений класса MethodArgumentNotValidException. Но он сломал все другие исключения, обрабатываемые ResponseEntityExceptionHandler, и вернул для них пустое тело ответа.

Но исправить это просто, просто замените handleExceptionInternal на ResponseEntityExceptionHandler:

@ControllerAdvice
@Slf4j
class GlobalExceptionHandlers extends ResponseEntityExceptionHandler {

    /// ... code from previous snippet

    @Override
    protected ResponseEntity<Object> handleExceptionInternal(
                                      Exception exception, 
                                      Object body, 
                                      HttpHeaders headers, 
                                      HttpStatus status, 
                                      WebRequest request) {
        // for all exceptions that are not overriden, the body is null, so we can
        // just provide new body based on error message and call super method
        var apiError = Objects.isNull(body) 
                ? new ApiError(status, exception.getMessage()) // <-- 
                : body;
        return super.handleExceptionInternal(exception, apiError, headers, status, request);
    }
}
person Pawel Kiszka    schedule 28.04.2020
comment
Спасибо, вы спасли мне день. - person Heybat; 20.11.2020

Это ожидаемое поведение. Посмотрите исходный код класса ResponseEntityExceptionHandler.

@ExceptionHandler({
            org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException.class,
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            HttpMediaTypeNotAcceptableException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            MethodArgumentNotValidException.class,
            MissingServletRequestPartException.class,
            BindException.class,
            NoHandlerFoundException.class,
            AsyncRequestTimeoutException.class
        })
    public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {

Все эти исключения обрабатываются БЕЗ тела ответа. Вызывается общий метод:

//second parameter is body which is null
handleExceptionInternal(ex, null, headers, status, request)

Если вам нужно обрабатывать определенные исключения по-другому, переопределите их, например, где я хотел отправить собственный ответ для HttpMessageNotReadableException

 @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
        HttpHeaders headers, HttpStatus status, WebRequest request)
    {
        logger.error("handleHttpMessageNotReadable()", ex);
        ValidationErrors validationErrors = null;
        if (ex.getRootCause() instanceof InvalidFormatException) {
            InvalidFormatException jacksonDataBindInvalidFormatException = (InvalidFormatException) ex.getRootCause();
            validationErrors = new ValidationErrors(jacksonDataBindInvalidFormatException.getOriginalMessage());
        }
        headers.add("X-Validation-Failure", "Request validation failed !");
        return handleExceptionInternal(ex, validationErrors, headers, status, request);
    }
person TechFree    schedule 10.06.2019
comment
Когда у меня нет своего совета, Spring отправляет стек ошибок в теле ответа. Я хочу, чтобы Spring вела себя так же, за исключением методов, которые я переопределил. Например: я хочу предоставить свою собственную реализацию handleMethodArgumentNotValid, но мне нужна пружина для обработки handleHttpMediaTypeNotAcceptable - person Gagandeep Singh; 10.06.2019
comment
Что произойдет, если вы не расширите ResponseEntityExceptionHandler? Можете ли вы проверить это для настраиваемого исключения и для дескриптора исключения Spring MVCHttpMediaTypeNotAcceptable - person TechFree; 11.06.2019
comment
Поэтому, когда я не расширяюсь от ResponseEntityExceptionHandler, Spring начинает обработку с правильным телом ответа для handleHttpMediaTypeNotAcceptable - person Gagandeep Singh; 11.06.2019
comment
code ‹html› ‹body› ‹h1› Белая метка страницы с ошибкой ‹/h1› ‹p› В этом приложении нет явного сопоставления для / error, поэтому вы рассматриваете это как запасной вариант. ‹/P› ‹div id = 'created' ›Вт, 11 июня, 09:44:35 EDT 2019 ‹/div› ‹div› Произошла непредвиденная ошибка (тип = Not Acceptable, status = 406). ‹/Div› ‹div› Не удалось найти приемлемое представление ‹/div› ‹Div style = 'white-space: pre-wrap;'› org.springframework.web.HttpMediaTypeNotAcceptableException: не удалось найти приемлемое представление code - person Gagandeep Singh; 11.06.2019
comment
Хорошо, тогда не расширяйте responseEntity .... и просто добавьте свой собственный @ExceptionHandler ... @ExceptionHandler (RuntimeException.class) public ResponseEntity ‹Object› handleRuntimeException (RuntimeException ex, запрос WebRequest) - person TechFree; 11.06.2019
comment
Если я это сделаю, я не смогу обрабатывать handleMissingServletRequestParameter. Я хочу отреагировать на эту ошибку, следуя своему стандартному формату ответа json - person Gagandeep Singh; 11.06.2019

После использования @ControllerAdvice вам необходимо определить общую структуру исключений.

@ResponseBody
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse generationExceptionHandler(Exception e){
    log.info("Responding INTERNAL SERVER ERROR Exception");
    return new ErrorResponse(ServiceException.getSystemError());
}
person Sushil Behera    schedule 10.06.2019
comment
У меня есть следующее: @ResponseBody @ExceptionHandler (Exception.class) @ResponseStatus (HttpStatus.INTERNAL_SERVER_ERROR) protected ResponseEntity ‹Object› handleDefaultException (Exception ex) {logger.error (, ex); ApiError apiError = new ApiError (HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage (), ex); } return buildResponseEntity (apiError); } Итак, теперь, если я создам запрос без обязательного поля, ответ моему клиенту будет с пустым телом и заголовком 404. Даже мои точки останова внутри методов не срабатывают. - person Gagandeep Singh; 10.06.2019
comment
Вы расширили свой класс с помощью ResponseEntityExceptionHandler ?? - person Sushil Behera; 10.06.2019
comment
и переопределите этот метод protected ResponseEntity ‹Object› handleMethodArgumentNotValid (MethodArgumentNotValidException ex, заголовки HttpHeaders, статус HttpStatus, запрос WebRequest) - person Sushil Behera; 10.06.2019
comment
Я расширил свой класс, как я уже упоминал в описании. Моя проблема в том, что если я переопределю handleMethodArgumentNotValid, тогда потребуется моя реализация. Но если я этого не сделаю, то в качестве ответа не будет использоваться стандартная реализация отправки стека ошибок Spring. - person Gagandeep Singh; 10.06.2019