Festlegen der Priorität mehrerer @ControllerAdvice @ExceptionHandlers

82

Ich habe Multiplikatorklassen mit Anmerkungen versehen @ControllerAdvice, jede mit einer @ExceptionHandlerMethode in.

Man behandelt Exceptionmit der Absicht, dass, wenn kein spezifischerer Handler gefunden wird, dieser verwendet werden sollte.

Leider scheint Spring MVC immer den allgemeinsten Fall ( Exception) zu verwenden, anstatt spezifischere ( IOExceptionzum Beispiel).

Würde man erwarten, dass sich Spring MVC so verhält? Ich versuche, ein Muster aus Jersey zu emulieren, das jede ExceptionMapper(äquivalente Komponente) bewertet, um festzustellen, wie weit der deklarierte Typ, den es behandelt, von der ausgelösten Ausnahme entfernt ist, und immer den nächsten Vorfahren verwendet.

EngineerBetter_DJ
quelle

Antworten:

122

Würde man erwarten, dass sich Spring MVC so verhält?

Ab Spring 4.3.7 verhält sich Spring MVC folgendermaßen: Es verwendet HandlerExceptionResolverInstanzen, um Ausnahmen zu behandeln, die von Handler-Methoden ausgelöst werden.

Standardmäßig registriert die Web-MVC-Konfiguration eine einzelne HandlerExceptionResolverBean, a HandlerExceptionResolverComposite, die

delegiert an eine Liste von anderen HandlerExceptionResolvers.

Diese anderen Resolver sind

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

in dieser Reihenfolge registriert. Für den Zweck dieser Frage kümmern wir uns nur ExceptionHandlerExceptionResolver.

Eine AbstractHandlerMethodExceptionResolver, die Ausnahmen durch @ExceptionHandlerMethoden auflöst .

Bei der Kontextinitialisierung generiert Spring ControllerAdviceBeanfür jede mit @ControllerAdviceAnmerkungen versehene Klasse eine. Das ExceptionHandlerExceptionResolverruft diese aus dem Kontext ab und sortiert sie mit AnnotationAwareOrderComparatorwelcher

ist eine Erweiterung OrderComparator, die die Spring- Ordered Schnittstelle sowie die Annotationen @Orderund und unterstützt @Priority, wobei ein Bestellwert, der von einer geordneten Instanz bereitgestellt wird, einen statisch definierten Annotationswert (falls vorhanden) überschreibt.

Anschließend wird ExceptionHandlerMethodResolverfür jede dieser ControllerAdviceBeanInstanzen eine registriert (Zuordnung der verfügbaren @ExceptionHandlerMethoden zu den Ausnahmetypen, die behandelt werden sollen). Diese werden schließlich in derselben Reihenfolge zu a hinzugefügt LinkedHashMap(wodurch die Iterationsreihenfolge erhalten bleibt).

Wenn eine Ausnahme auftritt, ExceptionHandlerExceptionResolverdurchläuft der diese diese ExceptionHandlerMethodResolverund verwendet die erste, die die Ausnahme behandeln kann.

Der Punkt hier ist also: Wenn Sie ein @ControllerAdvicemit einem @ExceptionHandlerfür das haben Exception, wird es vor einer anderen @ControllerAdviceKlasse mit einem @ExceptionHandlerfür eine spezifischere Ausnahme registriert , wie zum Beispiel IOException, dass das erste aufgerufen wird. Wie bereits erwähnt, können Sie diese Registrierungsreihenfolge steuern, indem Sie Ihre mit @ControllerAdviceAnmerkungen versehene Klasse implementieren Orderedoder mit @Orderoder mit Anmerkungen versehen @Priorityund ihr einen geeigneten Wert geben.

Sotirios Delimanolis
quelle
5
Ferner wird im Fall mehrerer @ExceptionHandlerMethoden innerhalb von a @ControllerAdvicediejenige ausgewählt, die die spezifischste Oberklasse der ausgelösten Ausnahme behandelt.
Vijay Aggarwal
In Spring Boot 2.3.3 ist keine @ Order-Annotation für eine Unterklasse erforderlich, die einen Controller-Hinweis überschreibt. ExceptionHandler-Methode aus einer übergeordneten Controller-
developer1011
92

Sotirios Delimanolis war in seiner Antwort sehr hilfreich. Bei weiteren Untersuchungen stellten wir fest, dass der Code, der nach @ ControlAdvice-Annotationen sucht, im Frühjahr 3.2.4 auch das Vorhandensein von @ Order-Annotationen überprüft und die Liste der ControllerAdviceBeans sortiert.

Die resultierende Standardreihenfolge für alle Controller ohne die Annotation @Order lautet Ordered # LOWEST_PRECEDENCE. Wenn Sie also einen Controller haben, der die niedrigste Priorität haben muss, müssen ALLE Ihre Controller eine höhere Reihenfolge haben.

In diesem Beispiel wird gezeigt, wie zwei Exception-Handler-Klassen mit ControllerAdvice- und Order-Annotationen erstellt werden, die geeignete Antworten liefern können, wenn entweder eine UserProfileException oder eine RuntimeException auftritt.

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() {
        ....
    }
}
  • Siehe ControllerAdviceBean # initOrderFromBeanType ()
  • Siehe ControllerAdviceBean # findAnnotatedBeans ()
  • Siehe ExceptionHandlerExceptionResolver # initExceptionHandlerAdviceCache ()

Genießen!

Dominic Clifton
quelle
21

Die Reihenfolge der Ausnahmebehandlungsroutinen kann mithilfe der @OrderAnmerkung geändert werden .

Beispielsweise:

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 {

    //...

}

@OrderDer Wert kann eine beliebige Ganzzahl sein.

Koraktor
quelle
5

Ich fand auch in der Dokumentation, dass:

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

ExceptionHandlerMethod

protected ServletInvocableHandlerMethod getExceptionHandlerMethod (HandlerMethod handlerMethod, Ausnahme Ausnahme)

Suchen Sie eine @ ExcceptionHandler-Methode für die angegebene Ausnahme. Die Standardimplementierung sucht zuerst nach Methoden in der Klassenhierarchie des Controllers. Wenn sie nicht gefunden wird, sucht sie weiter nach zusätzlichen @ ExcceptionHandler-Methoden, sofern einige von @ControllerAdvice Spring verwaltete Beans erkannt wurden . Parameter: handlerMethod - Die Methode, bei der die Ausnahme ausgelöst wurde (möglicherweise null). Ausnahme - Die ausgelöste Ausnahme. Rückgabe: Eine Methode zur Behandlung der Ausnahme oder null

Wenn Sie dieses Problem lösen möchten, müssen Sie Ihren spezifischen Ausnahmebehandler innerhalb des Controllers hinzufügen, der diese Ausnahme auslöst. ANd, um ein einziges ControllerAdvice zu definieren, das den globalen Standardausnahmehandler behandelt.

Dies vereinfacht den Vorgang und wir benötigen keine Auftragsanmerkung, um das Problem zu beheben.

Mathieu Stn
quelle
2

Ähnliches gilt für den hervorragenden Beitrag " Ausnahmebehandlung im Frühjahr MVC " im Spring-Blog im Abschnitt " Globale Ausnahmebehandlung" . In diesem Szenario wird nach in der Ausnahmeklasse registrierten ResponseStatus-Annotationen gesucht und, falls vorhanden, die Ausnahme erneut ausgelöst, damit das Framework sie verarbeiten kann. Möglicherweise können Sie diese allgemeine Taktik anwenden - versuchen Sie festzustellen, ob es einen geeigneteren Handler gibt, und werfen Sie ihn neu.

Alternativ gibt es einige andere Strategien zur Ausnahmebehandlung, die Sie möglicherweise stattdessen betrachten.

pimlottc
quelle
1

Wichtige zu behandelnde Klasse:

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

Andere Ausnahmen mit niedriger Priorität

@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
quelle
0

Sie können auch einen Zahlenwert wie unten verwenden

@Order(value = 100)

Niedrigere Werte haben höhere Priorität. Der Standardwert ist * {@code Ordered.LOWEST_PRECEDENCE} und gibt die niedrigste Priorität an (Verlust gegenüber einem anderen * angegebenen Bestellwert).

Daria Yu
quelle