При создании контроллера в Spring Boot для обработки ошибок/исключений все особым образом, включая пользовательские исключения, какой метод следует предпочесть?
Должен ли контроллер реализовывать Spring Boot ErrorController?
Должен ли контроллер расширять Spring ResponseEntityExceptionHandler?
Оба: один контроллер, реализующий и расширяющий оба класса, включая обе их функциональности?
Оба: два отдельных контроллера, один реализующий ErrorController, другой расширяющий ResponseEntityExceptionHandler?
Причина этого поста — найти способ обработки исключений в Spring Boot с все следующих атрибутов:
Throwables, возникающие в контроллерах/фильтрах/перехватчиках во время обработки запроса, должны быть перехвачены.Throwable мы не делайте хотим предоставить любую трассировку стека или другие детали реализации клиенту Когда-либо (если это явно не закодировано таким образом).Throwable отдельно по их классам. Для любого другого неуказанного типа можно указать ответ по умолчанию. (Я точно знаю, что этот является возможен с @ExceptionHandler. Но ErrorController?)Я заметил, что оба контроллера (см. 1 и 2 выше) могут содержать методы, возвращающие объект ResponseEntity, тем самым обрабатывая возникшее исключение и возвращая ответ клиенту. Значит, теоретически они могут дать тот же результат?
Существует несколько руководств по использованию методов 1 и 2. Но я не нашел статей, рассматривающих оба варианта, сравнивающих их или использующих их вместе, что вызывает несколько дополнительных вопросов:
Стоит ли их вообще рассматривать вместе?
Каковы основные различия между этими двумя предложенными методами? В чем сходство?
Является ли один более мощной версией другого? Есть ли что-то, что один может сделать, а другой не может, и наоборот?
Можно ли их использовать вместе? Бывают ли ситуации, когда это будет необходимо?
Если они используются вместе, как будет обрабатываться исключение? Он проходит через оба обработчика или только через один? В случае последнего, какой?
Если они используются вместе, и возникает исключение внутри контроллера (одного или другого) во время обработки исключений, как будет обрабатываться это исключение? Он отправляется на другой контроллер? Могут ли исключения теоретически начать прыгать между контроллерами или создавать какой-либо другой невосстанавливающийся цикл?
Есть ли какая-либо доверенная/официальная документация о том, как Spring Boot использует Spring ResponseEntityExceptionHandler внутри или как он ожидает, что он будет использоваться в приложениях Spring Boot?
Если одного ResponseEntityExceptionHandler уже достаточно, то почему ErrorController существует?
Если посмотреть на ResponseEntityExceptionHandler Spring вместе с аннотацией @ExceptionHandler, кажется, что он более мощный в обработке разных типов исключений по отдельности и с использованием более чистого кода. Но поскольку Spring Boot построен поверх Spring, значит ли это:
ErrorController вместо расширения ResponseEntityExceptionHandler?ErrorController делать все, что может ResponseEntityExceptionHandler, включая обработку различных типов исключений по отдельности?Обновлено: Связано: Spring @ControllerAdvice против ErrorController




Я признаю, что я не слишком хорошо знаком с 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);
}
}
Приложение Spring Boot имеет конфигурацию по умолчанию для обработки ошибок — Эррормвкаутоконфигуратион.
Что он в основном делает, если не предусмотрена дополнительная настройка:
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.
Редактировать:
Чтобы не усложнять:
Означает ли это, что когда я расширяю ResponseEntityExceptionHandler и переопределяю некоторые из его методов, переопределенные методы мой будут автоматически использоваться для обработки исключений Spring MVC? Например, если я расширим этот класс, обнаружит ли Spring его и не будет использовать класс по умолчанию?
У меня есть собственная реализация ErrorController и @ExceptionHandler для пользовательского типа исключения. Если этот настраиваемый тип исключения вызывается внутри любого из моих контроллеров, по умолчанию он отправляется на ErrorController, а @ExceptionHandler игнорируется. Только если ErrorController бросает его дальше, то он отправляется в @ExceptionHandler. Это поведение противоположно тому, что вы описали, почему?
Последовательность такова: (1) обычный контроллер бросает -> (2) мой ErrorController ловит и выбрасывает -> (3) DispatcherServlet отправляет его AbstractHandlerExceptionResolver, который отправляет его моему @ExceptionHandler -> (4) мой @ExceptionHandler ловит и разрешает.
@anddero спасибо, я нашел в своем ответе ошибку, связанную с порядком выполнения DefaultHandlerExceptionResolver. Описанная вами последовательность заставляет меня думать, что исключение, которое привело к ErrorController, не обрабатывается ни одним из ваших методов с помощью @ExceptionHandler. Вот почему первая цепочка поиска преобразователя исключений привела вас прямо к вашему ErrorController. Добавьте точку останова в HandlerExceptionResolverComposite.resolveException, чтобы увидеть, сколько раз она останавливается там.
Ладно, думаю, теперь я понял. @ExceptionHandler перехватывает исключения только в том случае, если они выбрасываются из контроллеров. Но в одном случае моя реализация javax.servlet.Filter выдает исключение во время предварительной обработки. И в этом случае он сразу сопоставляется с /error и вместо этого отправляется в мою реализацию ErrorController. Но поскольку ErrorController является контроллером, если он выдает исключение дальше, его может обработать @ExceptionHandler.
Вы практически ответили на все вопросы. Тем не менее, я все еще ищу одну небольшую деталь: Может ли реализация ErrorController также проверять тип исключения, чтобы обрабатывать разные исключения по-другому? Если я найду ответ на этот вопрос, я награжу вас наградой, потому что вы дали мне именно то, что я искал :)
@anddero Однажды в ErrorController у HttpServletRequest есть набор атрибутов, который вы можете использовать для получения исключения: Throwable ex = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION). Исходное исключение, вероятно, будет заключено в Вложенное исключение сервлета.
Спасибо, похоже, подход по книге с использованием только
@ExceptionHandlers. Я могу понять и оценить чистоту этого предложения, но мне больше интересно узнать, почему Spring Boot решил ввестиErrorControllerи почему он очень часто продвигается в их учебниках. Я предполагаю, что это еще один способ сделать их структуру более абстрактной и более легкой для адаптации поверх Spring без большого количества кода и понимания базовой реализации. Я предлагаю это на основе целей Spring Boot в качестве основы и приведенного выше ответа от да-ша1.