Ustawianie pierwszeństwa wielu @ControllerAdvice @ExceptionHandlers

83

Mam klasy mnożników z adnotacjami @ControllerAdvice, każda z @ExceptionHandlermetodą w.

Jeden radzi sobie Exceptionz zamiarem, że jeśli nie zostanie znaleziony bardziej konkretny program obsługi, należy go użyć.

Niestety Spring MVC wydaje się zawsze używać najbardziej ogólnych przypadków ( Exception) zamiast bardziej szczegółowych ( IOExceptionna przykład).

Czy tak będzie się zachowywać Spring MVC? Próbuję emulować wzorzec z Jersey, który ocenia każdy ExceptionMapper(równoważny składnik), aby określić, jak daleko zadeklarowany typ, który obsługuje, jest od wyjątku, który został zgłoszony, i zawsze używa najbliższego przodka.

EngineerBetter_DJ
źródło

Odpowiedzi:

127

Czy tak będzie się zachowywać Spring MVC?

Począwszy od wersji Spring 4.3.7, tak zachowuje się Spring MVC: używa on HandlerExceptionResolverinstancji do obsługi wyjątków generowanych przez metody obsługi.

Domyślnie konfiguracja sieci Web MVC rejestruje pojedynczy HandlerExceptionResolverkomponent bean HandlerExceptionResolverComposite, który

delegaci na listę innych HandlerExceptionResolvers.

Te inne resolwery są

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

zarejestrowane w tej kolejności. Na potrzeby tego pytania tylko nam zależy ExceptionHandlerExceptionResolver.

AbstractHandlerMethodExceptionResolverŻe postanawia wyjątki poprzez @ExceptionHandlermetod.

Podczas inicjalizacji kontekstu Spring wygeneruje ControllerAdviceBeandla każdej @ControllerAdvicewykrytej klasy z adnotacjami. ExceptionHandlerExceptionResolverPobierze je z kontekstu i sortować je za pomocą za pomocą AnnotationAwareOrderComparatorktórego

jest rozszerzeniem OrderComparatorobsługującym Ordered interfejs Springa, a także adnotacje @Orderi @Priority, z wartością zamówienia dostarczoną przez instancję Ordered przesłaniającą statycznie zdefiniowaną wartość adnotacji (jeśli istnieje).

Następnie zarejestruje ExceptionHandlerMethodResolverdla każdego z tych ControllerAdviceBeanwystąpień (mapowanie dostępnych @ExceptionHandlermetod na typy wyjątków, które mają obsługiwać). Są one ostatecznie dodawane w tej samej kolejności do a LinkedHashMap(co zachowuje kolejność iteracji).

Gdy wystąpi wyjątek, ExceptionHandlerExceptionResolverwill iteruje przez nie ExceptionHandlerMethodResolveri używa pierwszego, który może obsłużyć wyjątek.

Chodzi o to, że jeśli masz a @ControllerAdvicez @ExceptionHandlerfor, Exceptionktóry zostanie zarejestrowany przed inną @ControllerAdviceklasą z @ExceptionHandlerbardziej szczegółowym wyjątkiem, na przykład IOException, zostanie wywołany ten pierwszy. Jak wspomniano wcześniej, możesz kontrolować kolejność rejestracji, @ControllerAdviceimplementując klasę z Orderedadnotacjami lub dodając do niej adnotację @Orderlub @Priorityi nadając jej odpowiednią wartość.

Sotirios Delimanolis
źródło
5
Ponadto, w przypadku wielu @ExceptionHandlermetod w ramach a @ControllerAdvice, wybierana jest ta, która obsługuje najbardziej szczegółową nadklasę rzuconego wyjątku.
Vijay Aggarwal,
Podczas wiosennego rozruchu 2.3.3 nie wymaga adnotacji @Order w podklasie przesłaniającej poradę kontrolera Metoda ExceptionHandler z klasy porad kontrolera nadrzędnego
Vadiraj Purohit
93

Sotirios Delimanolis był bardzo pomocny w udzieleniu odpowiedzi, po dalszych badaniach stwierdziliśmy, że wiosną 3.2.4 kod wyszukujący adnotacje @ControllerAdvice również sprawdza obecność adnotacji @Order i sortuje listę ControllerAdviceBeans.

Wynikowa domyślna kolejność dla wszystkich kontrolerów bez adnotacji @Order to Ordered # LOWEST_PRECEDENCE, co oznacza, że ​​jeśli masz jeden kontroler, który musi mieć najniższy priorytet, WSZYSTKIE kontrolery muszą mieć wyższą kolejność.

Oto przykład pokazujący, jak mieć dwie klasy obsługi wyjątków z adnotacjami ControllerAdvice i Order, które mogą obsługiwać odpowiednie odpowiedzi, gdy wystąpi wyjątek UserProfileException lub 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() {
        ....
    }
}
  • Zobacz ControllerAdviceBean # initOrderFromBeanType ()
  • Zobacz ControllerAdviceBean # findAnnotatedBeans ()
  • Zobacz ExceptionHandlerExceptionResolver # initExceptionHandlerAdviceCache ()

Cieszyć się!

Dominic Clifton
źródło
21

Kolejność obsługi wyjątków można zmienić za pomocą @Orderadnotacji.

Na przykład:

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 {

    //...

}

@OrderWartość może być dowolną liczbą całkowitą.

Koraktor
źródło
5

W dokumentacji znalazłem również, że:

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

ExceptionHandlerMethod

protected ServletInvocableHandlerMethod getExceptionHandlerMethod (HandlerMethod handlerMethod, wyjątek wyjątku)

Znajdź metodę @ExceptionHandler dla danego wyjątku. Domyślna implementacja najpierw wyszukuje metody w hierarchii klas kontrolera, a jeśli nie zostanie znaleziona, kontynuuje wyszukiwanie dodatkowych metod @ExceptionHandler, zakładając, że wykryto niektóre komponenty bean zarządzane przez @ControllerAdvice Spring . Parametry: handlerMethod - metoda, w której został zgłoszony wyjątek (może być zerowy) wyjątek - podniesiony wyjątek Returns: metoda do obsługi wyjątku lub null

Oznacza to, że jeśli chcesz rozwiązać ten problem, musisz dodać swoją określoną procedurę obsługi wyjątków w kontrolerze rzucającym te wyjątki. ANd, aby zdefiniować jeden i jedyny ControllerAdvice obsługujący globalną domyślną procedurę obsługi wyjątków.

Upraszcza to proces i nie potrzebujemy adnotacji do zamówienia, aby rozwiązać problem.

Mathieu Stn
źródło
2

Podobna sytuacja została przedstawiona w doskonałym poście „ Obsługa wyjątków w Spring MVC ” na blogu Spring, w sekcji zatytułowanej „ Obsługa wyjątków globalnych” . Ich scenariusz obejmuje sprawdzenie, czy adnotacje ResponseStatus zarejestrowane w klasie wyjątku, i jeśli są obecne, ponowne zgłoszenie wyjątku, aby platforma mogła je obsłużyć. Możesz użyć tej ogólnej taktyki - spróbuj określić, czy istnieje bardziej odpowiedni przewodnik i ponownie rzuć.

Alternatywnie, istnieje kilka innych strategii obsługi wyjątków, którym możesz się przyjrzeć.

pimlottc
źródło
1

Ważna klasa do obsłużenia:

**@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);
    }
}

Inne wyjątki o niskim priorytecie

@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());
    }
    }
Joyal Joseph
źródło
0

możesz również użyć wartości liczbowej, jak poniżej

@Order(value = 100)

Niższe wartości mają wyższy priorytet. Wartość domyślna to * {@code Ordered.LOWEST_PRECEDENCE}, wskazująca najniższy priorytet (przegrana z dowolną inną * określoną wartością zamówienia)

Daria Yu
źródło