Встановлення переваги декількох @ControllerAdvice @ExceptionHandlers


83

У мене є мультиплікаторські класи, анотовані @ControllerAdvice, кожен із @ExceptionHandlerметодом.

Хтось обробляє Exceptionз наміром, що якщо не буде знайдено більш конкретного обробника, це слід використовувати.

На жаль, Spring MVC, здається, завжди використовує найбільш загальний випадок ( Exception), а не більш конкретний ( IOExceptionнаприклад).

Невже так можна очікувати, що Spring MVC поводитиметься? Я намагаюся емулювати шаблон з Джерсі, який оцінює кожен ExceptionMapper(еквівалентний компонент), щоб визначити, наскільки заявлений тип, яким він обробляється, знаходиться від виключення, яке було видано, і завжди використовує найближчого предка.

Відповіді:


127

Невже так можна очікувати, що Spring MVC поводитиметься?

Станом на Spring 4.3.7, ось як поводиться Spring MVC: він використовує HandlerExceptionResolverекземпляри для обробки винятків, виданих методами обробника.

За замовчуванням веб-конфігурація MVC реєструє один HandlerExceptionResolverкомпонент a HandlerExceptionResolverComposite, який

делегатів до списку інших HandlerExceptionResolvers.

Ці інші вирішувачі

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

зареєстровано в такому порядку. Для цілей цього питання ми лише піклуємось ExceptionHandlerExceptionResolver.

Те, AbstractHandlerMethodExceptionResolverщо вирішує винятки за допомогою @ExceptionHandlerметодів.

При ініціалізації контексту Spring генерує ControllerAdviceBeanдля кожного @ControllerAdviceанотованого класу, який виявляє. ExceptionHandlerExceptionResolverБуде витягувати їх з контексту, і сортувати їх , використовуючи при допомоги AnnotationAwareOrderComparatorяких

є розширенням, OrderComparatorяке підтримує Ordered інтерфейс Spring, а також анотації @Orderand @Priority, зі значенням замовлення, що надається впорядкованим екземпляром, замінюючи статично визначене значення анотації (якщо воно є).

Потім він реєструє ExceptionHandlerMethodResolverдля кожного з цих ControllerAdviceBeanекземплярів (зіставлення доступних @ExceptionHandlerметодів із типами винятків, з якими вони мають працювати). Вони нарешті додаються в тому ж порядку до LinkedHashMap(який зберігає порядок ітерацій).

Коли виникає виняток, ExceptionHandlerExceptionResolverволя перебирає їх ExceptionHandlerMethodResolverі використовує перший, який може обробляти виняток.

Так точки тут: якщо у вас є @ControllerAdviceз @ExceptionHandlerдля Exceptionщо реєструються перед іншим @ControllerAdviceкласом з @ExceptionHandlerдля більш конкретного винятку, як IOException, що перші з них будуть викликатися. Як вже згадувалося раніше, ви можете керувати цим порядком реєстрації, @ControllerAdviceреалізувавши свій анотований клас Orderedабо анотуючи його за допомогою @Orderабо @Priorityі надаючи йому відповідне значення.


5
Далі, у випадку кількох @ExceptionHandlerметодів у межах a @ControllerAdvice, вибирається той, який обробляє найбільш конкретний суперклас викинутого винятку.
Vijay Aggarwal

У весняному завантаженні 2.3.3 він не вимагає анотації @Order для підкласу, який замінює метод рекомендації контролера ExceptionHandler з батьківського класу поради контролера
Vadiraj Purohit

93

Sotirios Delimanolis дуже допоміг у своїй відповіді, під час подальшого розслідування ми виявили, що навесні 3.2.4, так чи інакше, код, який шукає анотації @ControllerAdvice, також перевіряє наявність анотацій @Order та сортує список ControllerAdviceBeans.

Отриманий порядок за замовчуванням для всіх контролерів без анотації @Order впорядкований # LOWEST_PRECEDENCE, що означає, що якщо у вас є один контролер, який повинен мати найнижчий пріоритет, тоді ВСІ ваші контролери повинні мати вищий порядок.

Ось приклад, що показує, як мати два класи обробників винятків з анотаціями ControllerAdvice і Order, які можуть обслуговувати відповідні відповіді, коли відбувається або UserProfileException, або RuntimeException.

class UserProfileException extends RuntimeException {
}

@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
class UserProfileExceptionHandler {
    @ExceptionHandler(UserProfileException)
    @ResponseBody
    ResponseEntity<ErrorResponse> handleUserProfileException() {
        ....
    }
}

@ControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
class DefaultExceptionHandler {

    @ExceptionHandler(RuntimeException)
    @ResponseBody
    ResponseEntity<ErrorResponse> handleRuntimeException() {
        ....
    }
}
  • Див. ControllerAdviceBean # initOrderFromBeanType ()
  • Див. ControllerAdviceBean # findAnnotatedBeans ()
  • Див. ExceptionHandlerExceptionResolver # initExceptionHandlerAdviceCache ()

Насолоджуйтесь!


21

Порядок обробників винятків можна змінити за допомогою @Orderанотації.

Наприклад:

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ControllerAdvice;

@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomExceptionHandler {

    //...

}

@OrderЗначенням може бути будь-яке ціле число.


5

У документації я також виявив, що:

https://docs.spring.io/spring-framework/docs/4.3.4.RELEASE/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.html#getExceptionHandlerMethod-org.sp. web.method.HandlerMethod-java.lang.Exception-

ExceptionHandlerMethod

захищений ServletInvocableHandlerMethod getExceptionHandlerMethod (HandlerMethod handlerMethod, виняток виняток)

Знайдіть метод @ExceptionHandler для даного винятку. Реалізація за замовчуванням шукає спочатку методи в ієрархії класів контролера, і якщо їх не знайти, вона продовжує пошук додаткових методів @ExceptionHandler, припускаючи, що були виявлені деякі компоненти, керовані весною @ControllerAdvice . Параметри: handlerMethod - метод, де було виняток виняток (може бути нульовим) виняток - підняте виняток Повертає: метод для обробки винятку або null

Отже, це означає, що якщо ви хочете вирішити цю проблему, вам потрібно буде додати свій конкретний обробник винятків до контролера, який викидає ці винятки. І для визначення одного-єдиного ControllerAdvice, який обробляє глобальний обробник винятків за замовчуванням.

Це спрощує процес, і нам не потрібна анотація Order для вирішення проблеми.


2

Подібна ситуація висвітлена у чудовому дописі " Обробка винятків у Spring MVC " у блозі Spring, у розділі " Глобальна обробка винятків" . Їхній сценарій передбачає перевірку анотацій ResponseStatus, зареєстрованих у класі винятків, і, якщо вони є, повторне створення винятку, щоб фреймворк обробляв їх. Можливо, ви зможете скористатися цією загальною тактикою - спробуйте визначити, чи існує там більш підходящий обробник та перекидання.

Крім того, є деякі інші розглянуті стратегії обробки винятків, на які ви могли б звернути увагу.


1

Важливий клас, який потрібно обробляти:

**@Order(Ordered.HIGHEST_PRECEDENCE)**
public class FunctionalResponseEntityExceptionHandler {
    private final Logger logger = LoggerFactory.getLogger(FunctionalResponseEntityExceptionHandler.class);

    @ExceptionHandler(EntityNotFoundException.class)
    public final ResponseEntity<Object> handleFunctionalExceptions(EntityNotFoundException ex, WebRequest request)
    {
        logger.error(ex.getMessage() + " " + ex);
        ExceptionResponse exceptionResponse= new ExceptionResponse(new Date(), ex.getMessage(),
                request.getDescription(false),HttpStatus.NOT_FOUND.toString());
        return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
    }
}

Інші винятки з низьким пріоритетом

@ControllerAdvice
    public class GlobalResponseEntityExceptionHandler extends ResponseEntityExceptionHandler
    {
    private final Logger logger = LoggerFactory.getLogger(GlobalResponseEntityExceptionHandler.class);
    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllException(Exception ex, WebRequest request)
    {
        logger.error(ex.getMessage()+ " " + ex);
        ExceptionResponse exceptionResponse= new ExceptionResponse(new Date(), ex.toString(),
                request.getDescription(false),HttpStatus.INTERNAL_SERVER_ERROR.toString());
    }
    }

0

Ви також можете використовувати числове значення, як показано нижче

@Order(value = 100)

Нижні значення мають вищий пріоритет. Значенням за замовчуванням є * {@code Ordered.LOWEST_PRECEDENCE}, що вказує на найнижчий пріоритет (втрата для будь-якого іншого * вказаного значення замовлення)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.