Использование Spring Boot ErrorController и Spring ResponseEntityExceptionHandler правильно - PullRequest
4 голосов
/ 11 марта 2019

Вопрос

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

  1. Должен ли контроллер реализовывать загрузочную пружину ErrorController?

  2. Должен ли контроллер выдвигать пружину ResponseEntityExceptionHandler?

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

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

Цель

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

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

Подробнее

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

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

  1. Должны ли они даже рассматриваться вместе?

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

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

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

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

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

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

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

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

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

РЕДАКТИРОВАТЬ: Связанные: Spring @ControllerAdvice vs ErrorController

Ответы [ 2 ]

7 голосов
/ 15 марта 2019

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

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

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

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

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

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

Есть интересная информация в BasicErrorController javadoc :

Основной глобальный контроллер ошибок, рендеринг ErrorAttributes. Более конкретные ошибки могут быть обработаны либо с помощью абстракций Spring MVC (например, @ ExceptionHandler ), либо путем добавления страниц ошибок сервера сервлетов.

Реализация

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

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

Удобный базовый класс для классов @ControllerAdvice, которые хотят обеспечить централизованную обработку исключений для всех методов @RequestMapping с помощью методов @ExceptionHandler. Этот базовый класс предоставляет метод @ExceptionHandler для обработки внутренних исключений Spring MVC.

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

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

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

Edit:

Для простоты:

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

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

@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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...