Définition de la priorité de plusieurs @ControllerAdvice @ExceptionHandlers

J’ai des classes de multiplicateurs annotées avec @ControllerAdvice , chacune avec une méthode @ExceptionHandler dans.

On gère les Exception avec l’intention que si aucun gestionnaire spécifique n’est trouvé, cela devrait être utilisé.

Malheureusement, Spring MVC semble utiliser toujours le cas le plus générique ( Exception ) plutôt que des cas plus spécifiques ( IOException par exemple).

Est-ce que c’est ainsi que l’on pourrait s’attendre à ce que Spring MVC se comporte? J’essaie d’émuler un modèle de Jersey, qui évalue chaque ExceptionMapper (composant équivalent) pour déterminer la distance à laquelle le type déclaré qu’il gère provient de l’exception qui a été lancée et utilise toujours l’ancêtre le plus proche.

Est-ce que c’est ainsi que l’on pourrait s’attendre à ce que Spring MVC se comporte?

À partir de Spring 4.3.7, voici comment Spring MVC se comporte: il utilise les instances de HandlerExceptionResolver pour gérer les exceptions HandlerExceptionResolver par les méthodes de gestionnaire.

Par défaut, la configuration Web MVC enregistre un bean HandlerExceptionResolver unique, HandlerExceptionResolverComposite , qui

délègue à une liste d’autres HandlerExceptionResolvers .

Ces autres résolveurs sont

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

enregistré dans cet ordre. Dans le cadre de cette question, nous nous préoccupons uniquement d’ ExceptionHandlerExceptionResolver .

Un AbstractHandlerMethodExceptionResolver qui résout les exceptions via les méthodes @ExceptionHandler .

Lors de l’initialisation du contexte, Spring générera un ControllerAdviceBean pour chaque classe annotée @ControllerAdvice qu’il détecte. ExceptionHandlerExceptionResolver les récupère dans le contexte et les sortinge à l’aide d’ AnnotationAwareOrderComparator qui

est une extension de OrderComparator qui prend en charge l’interface Ordered de Spring ainsi que les annotations @Order et @Priority , avec une valeur d’ordre fournie par une instance Ordered qui remplace une valeur d’annotation définie statiquement (le cas échéant).

Il enregistrera ensuite un ExceptionHandlerMethodResolver pour chacune de ces instances ControllerAdviceBean (mappage des méthodes @ExceptionHandler disponibles sur les types d’exception à gérer). Ceux-ci sont finalement ajoutés dans le même ordre à un LinkedHashMap (qui préserve l’ordre des itérations).

Lorsqu’une exception se produit, ExceptionHandlerExceptionResolver parcourt ces ExceptionHandlerMethodResolver et utilise le premier capable de gérer l’exception.

Donc, le point ici est: si vous avez un @ControllerAdvice avec un @ExceptionHandler for Exception qui est enregistré avant une autre classe @ControllerAdvice avec un @ExceptionHandler pour une exception plus spécifique, comme IOException , ce premier sera appelé. Comme mentionné précédemment, vous pouvez contrôler cet ordre d’inscription en @ControllerAdvice votre @ControllerAdvice classe annoté @ControllerAdvice classer ou de l’annoter avec @Order ou @Priority et de lui donner une valeur appropriée.

Sotirios Delimanolis a été très utile dans sa réponse. Après un examen plus approfondi, nous avons constaté qu’au spring 3.2.4, le code qui recherche les annotations @ControllerAdvice vérifie également la présence des annotations @Order et sortinge la liste des ControllerAdviceBeans.

L’ordre par défaut résultant pour tous les contrôleurs sans l’annotation @Order est Ordered # LOWEST_PRECEDENCE, ce qui signifie que si vous avez un contrôleur qui doit avoir la priorité la plus basse, alors TOUS vos contrôleurs doivent avoir un ordre supérieur.

Voici un exemple montrant comment disposer de deux classes de gestionnaires d’exceptions avec les annotations ControllerAdvice et Order qui peuvent répondre aux réponses appropriées lorsqu’une exception UserProfileException ou RuntimeException se produit.

 class UserProfileException extends RuntimeException { } @ControllerAdvice @Order(Ordered.HIGHEST_PRECEDENCE) class UserProfileExceptionHandler { @ExceptionHandler(UserProfileException) @ResponseBody ResponseEntity handleUserProfileException() { .... } } @ControllerAdvice @Order(Ordered.LOWEST_PRECEDENCE) class DefaultExceptionHandler { @ExceptionHandler(RuntimeException) @ResponseBody ResponseEntity handleRuntimeException() { .... } } 
  • Voir ControllerAdviceBean # initOrderFromBeanType ()
  • Voir ControllerAdviceBean # findAnnotatedBeans ()
  • Voir ExceptionHandlerExceptionResolver # initExceptionHandlerAdviceCache ()

Prendre plaisir!

L’ordre des gestionnaires d’exceptions peut être modifié à l’aide de l’annotation @Order .

Par exemple:

 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 { //... } 

La valeur de @Order peut être n’importe quel entier.

Une situation similaire a convergé dans l’excellent article ” Gestion des exceptions dans Spring MVC ” sur le blog de Spring, dans la section intitulée Gestion globale des exceptions . Leur scénario implique la vérification des annotations ResponseStatus enregistrées sur la classe d’exception et, le cas échéant, le renvoi de l’exception pour permettre à l’infrastructure de les gérer. Vous pourriez être en mesure d’utiliser cette tactique générale – essayez de déterminer s’il existe un gestionnaire plus approprié et de le retracer.

Alternativement, il existe d’autres stratégies de gestion des exceptions que vous pourriez envisager à la place.

J’ai aussi trouvé dans la documentation que:

ExceptionHandlerMethod

protected ServletInvocableHandlerMethod getExceptionHandlerMethod (HandlerMethod handlerMethod, Exception d’exception)

Recherchez une méthode @ExceptionHandler pour l’exception donnée. L’implémentation par défaut recherche d’abord les méthodes dans la hiérarchie des classes du contrôleur et si elle n’est pas trouvée, elle continue de rechercher des méthodes @ExceptionHandler supplémentaires en supposant que des beans gérés par @ControllerAdvice Spring ont été détectés . Paramètres: handlerMethod – la méthode où l’exception a été déclenchée (peut être null) exception – l’exception déclenchée. Retourne: une méthode pour gérer l’exception, ou null

Cela signifie donc que si vous souhaitez résoudre ce problème, vous devrez append votre gestionnaire d’exceptions spécifique au contrôleur qui lancera ces exceptions. Et pour définir un seul ControllerAdvice qui gère le gestionnaire d’exceptions par défaut global.

Cela simplifie le processus et nous n’avons pas besoin de l’annotation de commande pour gérer le problème.