Правильное использование Spring Boot ErrorController и Spring ResponseEntityExceptionHandler

Вопрос

При создании контроллера в Spring Boot для обработки ошибок/исключений все особым образом, включая пользовательские исключения, какой метод следует предпочесть?

  1. Должен ли контроллер реализовывать Spring Boot ErrorController?

  2. Должен ли контроллер расширять Spring ResponseEntityExceptionHandler?

  3. Оба: один контроллер, реализующий и расширяющий оба класса, включая обе их функциональности?

  4. Оба: два отдельных контроллера, один реализующий ErrorController, другой расширяющий ResponseEntityExceptionHandler?

Цель

Причина этого поста — найти способ обработки исключений в Spring Boot с все следующих атрибутов:

  • Все Throwables, возникающие в контроллерах/фильтрах/перехватчиках во время обработки запроса, должны быть перехвачены.
  • В случае перехвата Throwable мы не делайте хотим предоставить любую трассировку стека или другие детали реализации клиенту Когда-либо (если это явно не закодировано таким образом).
  • Должна быть возможность обрабатывать все возникающие Throwable отдельно по их классам. Для любого другого неуказанного типа можно указать ответ по умолчанию. (Я точно знаю, что этот является возможен с @ExceptionHandler. Но ErrorController?)
  • Код должен быть как можно более чистым и явным, без уродливых обходных путей или UB для достижения этой цели.

Подробнее

Я заметил, что оба контроллера (см. 1 и 2 выше) могут содержать методы, возвращающие объект ResponseEntity, тем самым обрабатывая возникшее исключение и возвращая ответ клиенту. Значит, теоретически они могут дать тот же результат?

Существует несколько руководств по использованию методов 1 и 2. Но я не нашел статей, рассматривающих оба варианта, сравнивающих их или использующих их вместе, что вызывает несколько дополнительных вопросов:

  1. Стоит ли их вообще рассматривать вместе?

  2. Каковы основные различия между этими двумя предложенными методами? В чем сходство?

  3. Является ли один более мощной версией другого? Есть ли что-то, что один может сделать, а другой не может, и наоборот?

  4. Можно ли их использовать вместе? Бывают ли ситуации, когда это будет необходимо?

  5. Если они используются вместе, как будет обрабатываться исключение? Он проходит через оба обработчика или только через один? В случае последнего, какой?

  6. Если они используются вместе, и возникает исключение внутри контроллера (одного или другого) во время обработки исключений, как будет обрабатываться это исключение? Он отправляется на другой контроллер? Могут ли исключения теоретически начать прыгать между контроллерами или создавать какой-либо другой невосстанавливающийся цикл?

  7. Есть ли какая-либо доверенная/официальная документация о том, как Spring Boot использует Spring ResponseEntityExceptionHandler внутри или как он ожидает, что он будет использоваться в приложениях Spring Boot?

  8. Если одного ResponseEntityExceptionHandler уже достаточно, то почему ErrorController существует?

Если посмотреть на ResponseEntityExceptionHandler Spring вместе с аннотацией @ExceptionHandler, кажется, что он более мощный в обработке разных типов исключений по отдельности и с использованием более чистого кода. Но поскольку Spring Boot построен поверх Spring, значит ли это:

  • При использовании Spring Boot мы должны реализовать ErrorController вместо расширения ResponseEntityExceptionHandler?
  • Может ли ErrorController делать все, что может ResponseEntityExceptionHandler, включая обработку различных типов исключений по отдельности?

Обновлено: Связано: Spring @ControllerAdvice против ErrorController

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
23
0
14 884
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я признаю, что я не слишком хорошо знаком с ErrorController Spring, но, глядя на указанные вами цели, я считаю, что все они могут быть достигнуты с помощью Spring @ControllerAdvice. Ниже приведен пример того, как я использую его в своих собственных приложениях:

@ControllerAdvice
public class ExceptionControllerAdvice {

    private static final String INCOMING_REQUEST_FAILED = "Incoming request failed:";
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionControllerAdvice.class);
    private final MessageSource source;

    public ExceptionControllerAdvice2(final MessageSource messageSource) {
        source = messageSource;
    }

    @ExceptionHandler(value = {CustomException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorMessage badRequest(final CustomException ex) {
        LOGGER.error(INCOMING_REQUEST_FAILED, ex);
        final String message = source.getMessage("exception.BAD_REQUEST", null, LocaleContextHolder.getLocale());
        return new ErrorMessage(HttpStatus.BAD_REQUEST.value(), message);
    }

    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorMessage internalServerError(final Exception ex) {
        LOGGER.error(INCOMING_REQUEST_FAILED, ex);
        final String message =
                source.getMessage("exception.INTERNAL_SERVER_ERROR", null, LocaleContextHolder.getLocale());
        return new ErrorMessage(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
    }
}

Спасибо, похоже, подход по книге с использованием только @ExceptionHandlers. Я могу понять и оценить чистоту этого предложения, но мне больше интересно узнать, почему Spring Boot решил ввести ErrorController и почему он очень часто продвигается в их учебниках. Я предполагаю, что это еще один способ сделать их структуру более абстрактной и более легкой для адаптации поверх Spring без большого количества кода и понимания базовой реализации. Я предлагаю это на основе целей Spring Boot в качестве основы и приведенного выше ответа от да-ша1.

Snackoverflow 15.03.2019 17:27
Ответ принят как подходящий

Приложение Spring Boot имеет конфигурацию по умолчанию для обработки ошибок — Эррормвкаутоконфигуратион.

Что он в основном делает, если не предусмотрена дополнительная настройка:

  • он создает глобальный контроллер ошибок по умолчанию - BasicErrorController
  • он создает статическое представление «ошибка» по умолчанию «Страница ошибки Whitelabel».

BasicErrorController по умолчанию подключен к «/ error». Если в приложении нет настроенного представления «ошибка», в случае исключения, выданного любым контроллером, пользователь попадает на страницу с белой меткой /error, заполненную информацией с помощью BasicErrorController.

Если в приложении есть контроллер, реализующий ErrorController, это заменяетBasicErrorController.

Если в контроллере обработки ошибок возникает какое-либо исключение, оно проходит через фильтр исключений Spring (подробнее см. ниже) и, наконец, если ничего не найдено, это исключение будет обрабатываться базовым контейнером приложения, например. Кот. Базовый контейнер обработает исключение и покажет некоторую страницу/сообщение об ошибке в зависимости от его реализации.

В BasicErrorControllerjavadoc есть интересная информация:

Basic global error Controller, rendering ErrorAttributes. More specific errors can be handled either using Spring MVC abstractions (e.g. @ExceptionHandler) or by adding servlet server error pages.

BasicErrorController или ErrorController реализация — это глобальный обработчик ошибок. Его можно использовать вместе с @ExceptionHandler.

Здесь мы подходим к ResponseEntityExceptionHandler

A convenient base class for @ControllerAdvice classes that wish to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods. This base class provides an @ExceptionHandler method for handling internal Spring MVC exceptions.

Другими словами, это означает, что ResponseEntityExceptionHandler — это просто удобный класс, который уже содержит обработку исключений Spring MVC. И мы можем использовать его в качестве базового класса для нашего пользовательского класса для обработки исключений контроллеров. Чтобы наш пользовательский класс работал, он должен быть аннотирован @ControllerAdvice.

Классы, аннотированные @ControllerAdvice, можно использовать одновременно с глобальным обработчиком ошибок (реализацией BasicErrorController или ErrorController). Если наш аннотированный класс @ControllerAdvice (который может/или не расширять ResponseEntityExceptionHandler) не обрабатывает какое-то исключение, оно переходит в глобальный обработчик ошибок.

До сих пор мы рассматривали контроллер ErrorHandler и все, что помечено @ControllerAdvice. Но это намного сложнее. Я нашел действительно ценную информацию в вопросе - Установка приоритета нескольких @ControllerAdvice @ExceptionHandlers.

Редактировать:

Чтобы не усложнять:

  1. First Spring ищет обработчик исключений (метод с аннотацией @ExceptionHandler) в классах @ControllerAdvice. См. ExceptionHandlerExceptionResolver.
  2. Затем он проверяет, аннотировано ли выброшенное исключение @ResponseStatus или является производным от ResponseStatusException. См. ResponseStatusExceptionResolver.
  3. Затем он проходит через обработчики исключений Spring MVC по умолчанию. См. DefaultHandlerExceptionResolver.
  4. И в конце, если ничего не найдено, элемент управления перенаправляется на представление страницы с ошибкой, за которой стоит глобальный обработчик ошибок. Этот шаг не выполняется, если исключение исходит от самого обработчика ошибок.
  5. Если представление ошибок не найдено (например, глобальный обработчик ошибок отключен) или шаг 4 пропущен, то исключение обрабатывается контейнером.

Означает ли это, что когда я расширяю ResponseEntityExceptionHandler и переопределяю некоторые из его методов, переопределенные методы мой будут автоматически использоваться для обработки исключений Spring MVC? Например, если я расширим этот класс, обнаружит ли Spring его и не будет использовать класс по умолчанию?

Snackoverflow 15.03.2019 17:18

У меня есть собственная реализация ErrorController и @ExceptionHandler для пользовательского типа исключения. Если этот настраиваемый тип исключения вызывается внутри любого из моих контроллеров, по умолчанию он отправляется на ErrorController, а @ExceptionHandler игнорируется. Только если ErrorController бросает его дальше, то он отправляется в @ExceptionHandler. Это поведение противоположно тому, что вы описали, почему?

Snackoverflow 15.03.2019 20:59

Последовательность такова: (1) обычный контроллер бросает -> (2) мой ErrorController ловит и выбрасывает -> (3) DispatcherServlet отправляет его AbstractHandlerExceptionResolver, который отправляет его моему @ExceptionHandler -> (4) мой @ExceptionHandler ловит и разрешает.

Snackoverflow 15.03.2019 21:05

@anddero спасибо, я нашел в своем ответе ошибку, связанную с порядком выполнения DefaultHandlerExceptionResolver. Описанная вами последовательность заставляет меня думать, что исключение, которое привело к ErrorController, не обрабатывается ни одним из ваших методов с помощью @ExceptionHandler. Вот почему первая цепочка поиска преобразователя исключений привела вас прямо к вашему ErrorController. Добавьте точку останова в HandlerExceptionResolverComposite.resolveException, чтобы увидеть, сколько раз она останавливается там.

da-sha1 16.03.2019 01:16

Ладно, думаю, теперь я понял. @ExceptionHandler перехватывает исключения только в том случае, если они выбрасываются из контроллеров. Но в одном случае моя реализация javax.servlet.Filter выдает исключение во время предварительной обработки. И в этом случае он сразу сопоставляется с /error и вместо этого отправляется в мою реализацию ErrorController. Но поскольку ErrorController является контроллером, если он выдает исключение дальше, его может обработать @ExceptionHandler.

Snackoverflow 16.03.2019 11:48

Вы практически ответили на все вопросы. Тем не менее, я все еще ищу одну небольшую деталь: Может ли реализация ErrorController также проверять тип исключения, чтобы обрабатывать разные исключения по-другому? Если я найду ответ на этот вопрос, я награжу вас наградой, потому что вы дали мне именно то, что я искал :)

Snackoverflow 16.03.2019 11:58

@anddero Однажды в ErrorController у HttpServletRequest есть набор атрибутов, который вы можете использовать для получения исключения: Throwable ex = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION). Исходное исключение, вероятно, будет заключено в Вложенное исключение сервлета.

da-sha1 16.03.2019 20:52

Другие вопросы по теме