Установка приоритета нескольких @ControllerAdvice @ExceptionHandlers

83

У меня есть классы мультиплеров, аннотированные @ControllerAdvice, каждый с @ExceptionHandlerметодом в.

Один обрабатывает Exceptionс намерением, что если не найден более конкретный обработчик, его следует использовать.

К сожалению, Spring MVC, похоже, всегда использует наиболее общий case ( Exception), а не более конкретные ( IOExceptionнапример).

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

ИнженерBetter_DJ
источник

Ответы:

127

Это то, как можно ожидать поведения Spring MVC?

Начиная с Spring 4.3.7, Spring MVC ведет себя следующим образом: он использует HandlerExceptionResolverэкземпляры для обработки исключений, генерируемых методами обработчика.

По умолчанию конфигурация веб-MVC регистрирует один HandlerExceptionResolverbean-компонент a HandlerExceptionResolverComposite, который

делегаты к списку других HandlerExceptionResolvers.

Остальные резолверы

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

зарегистрированы в таком порядке. Мы заботимся только об этом вопросе ExceptionHandlerExceptionResolver.

Объект, AbstractHandlerMethodExceptionResolverкоторый разрешает исключения с помощью @ExceptionHandlerметодов.

При инициализации контекста Spring будет генерировать a ControllerAdviceBeanдля каждого @ControllerAdviceобнаруженного аннотированного класса. Они ExceptionHandlerExceptionResolverбудут извлекать их из контекста и сортировать их, используя AnnotationAwareOrderComparatorwhich

является расширением , OrderComparatorкоторое поддерживает в Spring Ordered интерфейс, а также @Orderи @Priorityаннотаций, со значением порядка , предоставленной упорядоченном например Переопределение статически определенное значение аннотаций (если таковые имеются).

Затем он зарегистрирует ExceptionHandlerMethodResolverдля каждого из этих ControllerAdviceBeanэкземпляров (сопоставив доступные @ExceptionHandlerметоды с типами исключений, которые они должны обрабатывать). Наконец, они добавляются в том же порядке к LinkedHashMap(что сохраняет порядок итераций).

Когда возникает исключение, он ExceptionHandlerExceptionResolverбудет перебирать их ExceptionHandlerMethodResolverи использовать первый, который может обработать исключение.

Итак, суть в следующем: если у вас есть объект @ControllerAdviceс @ExceptionHandlerfor, Exceptionкоторый регистрируется перед другим @ControllerAdviceклассом с @ExceptionHandlerболее конкретным исключением, например IOException, будет вызван первый класс . Как упоминалось ранее, вы можете управлять этим порядком регистрации, @ControllerAdviceреализуя свой аннотированный класс Orderedили аннотируя его с помощью @Orderили @Priorityи давая ему соответствующее значение.

Сотириос Делиманолис
источник
5
Кроме того, в случае нескольких @ExceptionHandlerметодов в a @ControllerAdviceвыбирается тот, который обрабатывает наиболее конкретный суперкласс созданного исключения.
Виджей Аггарвал
В весенней загрузке 2.3.3 не требуется аннотация @Order для подкласса, переопределяющего метод ExceptionHandler совета контроллера из родительского класса рекомендаций контроллера
Вадирадж Пурохит
93

Сотириос Делиманолис очень помог в своем ответе. При дальнейшем исследовании мы обнаружили, что в весенней версии 3.2.4 в любом случае код, который ищет аннотации @ControllerAdvice, также проверяет наличие аннотаций @Order и сортирует список ControllerAdviceBeans.

Результирующий порядок по умолчанию для всех контроллеров без аннотации @Order - Ordered # 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#getExceptionHandworklerMetprhod-rameframeing. web.method.HandlerMethod-java.lang.Exception-

ExceptionHandlerMethod

защищенный ServletInvocableHandlerMethod getExceptionHandlerMethod (HandlerMethod handlerMethod, исключение исключения)

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

Таким образом, это означает, что если вы хотите решить эту проблему, вам нужно будет добавить свой конкретный обработчик исключений в контроллер, генерирующий это исключение. ANd, чтобы определить один-единственный ControllerAdvice, обрабатывающий глобальный обработчик исключений по умолчанию.

Это упрощает процесс, и нам не нужна аннотация Order для решения проблемы.

Матье Стн
источник
2

Похожая ситуация описана в отличном сообщении « Обработка исключений в Spring MVC » в блоге Spring в разделе, озаглавленном « Глобальная обработка исключений» . Их сценарий включает проверку аннотаций ResponseStatus, зарегистрированных в классе исключения, и повторное генерирование исключения, если оно есть, чтобы платформа могла их обработать. Возможно, вам удастся использовать эту общую тактику - попробуйте определить, есть ли более подходящий хендлер и повторите бросок.

В качестве альтернативы вы можете рассмотреть другие стратегии обработки исключений.

Pimlottc
источник
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}, что указывает на самый низкий приоритет (уступающий любому другому * указанному значению заказа).

Дарья Ю
источник