web-dev-qa-db-fra.com

Utiliser correctement ErrorController de Spring Boot et ResponseEntityExceptionHandler de Spring

La question

Lors de la création d'un contrôleur dans Spring Boot pour gérer toutes les erreurs/exceptions de manière personnalisée, y compris les exceptions personnalisées , quelle technique privilégier?

  1. Le contrôleur doit-il implémenter le ErrorController de Spring Boot?

  2. Le contrôleur doit-il étendre le ResponseEntityExceptionHandler de Spring?

  3. Les deux: un seul contrôleur implémentant et étendant les deux classes, y compris leurs deux fonctionnalités?

  4. Les deux: deux contrôleurs distincts, l'un implémentant ErrorController, l'autre étendant ResponseEntityExceptionHandler?

Le but

La raison de ce message est de trouver un moyen de gestion des exceptions dans Spring Boot avec tous des attributs suivants:

  • Tous les Throwable se produisant dans les contrôleurs/filtres/intercepteurs pendant le traitement d'une demande doivent être interceptés.
  • Dans le cas de la capture d'un Throwable, nous ne voulons pas exposer la trace de la pile ou d'autres détails d'implémentation au client jamais (sauf si explicitement codé de cette façon).
  • Il devrait être possible de gérer tous les Throwables survenus séparément par leur classe. Pour tout autre type non spécifié, une réponse par défaut peut être spécifiée. (Je sais avec certitude que cela est possible avec @ExceptionHandler. Mais ErrorController?)
  • Le code doit être aussi clair et explicite que possible, sans contournements ou UB moches pour atteindre cet objectif.

Plus de détails

J'ai remarqué que les deux contrôleurs (voir 1 et 2 ci-dessus) peuvent contenir des méthodes renvoyant un objet ResponseEntity, gérant ainsi l'exception survenue et renvoyant une réponse au client. Donc, ils pourraient en théorie produire le même résultat?

Il existe plusieurs didacticiels sur l'utilisation des techniques 1 et 2. Mais je n'ai trouvé aucun article considérant les deux options, les comparant ou les utilisant ensemble, ce qui soulève plusieurs questions supplémentaires:

  1. Doit-on même les considérer ensemble?

  2. Quelles sont les principales différences entre ces deux techniques proposées? Quelles sont les similitudes?

  3. L'un est-il une version plus puissante de l'autre? Y a-t-il quelque chose que l'on peut faire que l'autre ne peut pas et vice versa?

  4. Peuvent-ils être utilisés ensemble? Y a-t-il des situations où cela serait nécessaire?

  5. S'ils sont utilisés ensemble, comment une exception serait-elle gérée? Est-ce que cela passe par les deux gestionnaires ou un seul? Dans le cas de ce dernier, lequel?

  6. S'ils sont utilisés ensemble et qu'une exception est levée à l'intérieur le contrôleur (l'un ou l'autre) pendant la gestion des exceptions , comment cette exception serait-elle gérée? Est-il envoyé à l'autre contrôleur? Les exceptions pourraient-elles théoriquement commencer à rebondir entre les contrôleurs ou créer un autre type de boucle non récupérable?

  7. Existe-t-il une documentation fiable/officielle sur la façon dont Spring Boot utilise ResponseEntityExceptionHandler de Spring en interne ou comment il s'attend à ce qu'il soit utilisé dans les applications Spring Boot?

  8. Si ResponseEntityExceptionHandler seul suffit déjà, alors pourquoi ErrorController existe-t-il?

Lorsque vous regardez le ResponseEntityExceptionHandler du printemps avec le @ExceptionHandler annotation, il semble être plus puissant pour gérer séparément différents types d'exceptions et utiliser un code plus propre. Mais parce que Spring Boot est construit au-dessus de Spring, cela signifie-t-il:

  • Lorsque vous utilisez Spring Boot, nous devons implémenter ErrorController au lieu d'étendre ResponseEntityExceptionHandler?
  • ErrorController peut-il tout faire ResponseEntityExceptionHandler peut, y compris gérer différents types d'exceptions séparément?

EDIT: Connexes: Spring @ControllerAdvice vs ErrorController

8
anddero

L'application Spring Boot a une configuration par défaut pour la gestion des erreurs - ErrorMvcAutoConfiguration .

Ce qu'il fait essentiellement, si aucune configuration supplémentaire n'est fournie:

  • il crée un contrôleur d'erreur global par défaut - BasicErrorController
  • il crée une vue statique par défaut "erreur" "Page d'erreur Whitelabel".

BasicErrorController est câblé à '/ error' par défaut. S'il n'y a pas de vue "erreur" personnalisée dans l'application, en cas d'exception levée par un contrôleur, l'utilisateur accède à la page d'étiquette blanche/error, remplie d'informations par BasicErrorController.

Si l'application a un contrôleur implémentant ErrorController il remplace BasicErrorController.

Si une exception se produit dans le contrôleur de gestion des erreurs, elle passera par le filtre d'exception Spring (voir plus de détails ci-dessous) et enfin si rien n'est trouvé, cette exception sera gérée par le conteneur d'application sous-jacent, par ex. Matou. Le conteneur sous-jacent gérera l'exception et affichera une page/un message d'erreur en fonction de son implémentation.

Il y a une information intéressante dans BasicErrorControllerjavadoc :

Contrôleur d'erreurs globales de base, rendu ErrorAttributes. Des erreurs plus spécifiques peuvent être gérées soit en utilisant des abstractions Spring MVC (par exemple @ ExceptionHandler) soit en ajoutant des pages d'erreur du serveur de servlet.

L'implémentation BasicErrorController ou ErrorController est un gestionnaire d'erreur global . Il peut être utilisé conjointement avec @ExceptionHandler.

Nous arrivons ici à ResponseEntityExceptionHandler

Une classe de base pratique pour les classes @ControllerAdvice qui souhaitent fournir une gestion centralisée des exceptions dans toutes les méthodes @RequestMapping via les méthodes @ExceptionHandler. Cette classe de base fournit une méthode @ExceptionHandler pour gérer les exceptions Spring MVC internes.

En d'autres termes, cela signifie que ResponseEntityExceptionHandler n'est qu'une classe de commodité, qui contient déjà la gestion des exceptions Spring MVC. Et nous pouvons l'utiliser comme classe de base pour notre classe personnalisée pour gérer les exceptions des contrôleurs. Pour que notre classe personnalisée fonctionne, elle doit être annotée avec @ControllerAdvice.

Classes annotées avec @ControllerAdvice peut être utilisé en même temps que le gestionnaire d'erreur global (implémentation BasicErrorController ou ErrorController). Si notre @ControllerAdvice la classe annotée (qui peut ou non étendre ResponseEntityExceptionHandler) ne gère pas une exception, l'exception va au gestionnaire d'erreur global.

Jusqu'à présent, nous avons examiné ErrorHandler contrôleur et tout ce qui était annoté avec @ControllerAdvice. Mais c'est beaucoup plus compliqué. J'ai trouvé un aperçu vraiment précieux dans la question - Définition de la priorité de plusieurs @ControllerAdvice @ExceptionHandlers .

Modifier:

Pour faire simple:

  1. First Spring recherche un gestionnaire d'exceptions (une méthode annotée avec @ExceptionHandler) dans les classes @ControllerAdvice. Voir ExceptionHandlerExceptionResolver .
  2. Il vérifie ensuite si l'exception levée est annotée avec @ResponseStatus ou dérive de ResponseStatusException. Voir ResponseStatusExceptionResolver .
  3. Ensuite, il passe par les gestionnaires par défaut des exceptions Spring MVC. Voir DefaultHandlerExceptionResolver .
  4. Et à la fin, si rien n'est trouvé, le contrôle est transféré vers la page d'erreur avec le gestionnaire d'erreur global derrière lui. Cette étape n'est pas exécutée si l'exception provient du gestionnaire d'erreurs lui-même.
  5. Si aucune vue d'erreur n'est trouvée (par exemple, le gestionnaire d'erreur global est désactivé) ou l'étape 4 est ignorée, l'exception est gérée par le conteneur.
13
da-sha1

Je dois admettre que je ne suis pas trop familier avec ErrorController de Spring, mais en regardant vos objectifs spécifiés, je crois que tous peuvent être atteints proprement en utilisant @ControllerAdvice de Spring. Ce qui suit est un exemple de la façon dont je l'ai utilisé dans mes propres applications:

@ControllerAdvice
public class ExceptionControllerAdvice {

    private static final String INCOMING_REQUEST_FAILED = "Incoming request failed:";
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionControllerAdvice.class);
    private final MessageSource source;

    public ExceptionControllerAdvice2(final MessageSource messageSource) {
        source = messageSource;
    }

    @ExceptionHandler(value = {CustomException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorMessage badRequest(final CustomException ex) {
        LOGGER.error(INCOMING_REQUEST_FAILED, ex);
        final String message = source.getMessage("exception.BAD_REQUEST", null, LocaleContextHolder.getLocale());
        return new ErrorMessage(HttpStatus.BAD_REQUEST.value(), message);
    }

    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorMessage internalServerError(final Exception ex) {
        LOGGER.error(INCOMING_REQUEST_FAILED, ex);
        final String message =
                source.getMessage("exception.INTERNAL_SERVER_ERROR", null, LocaleContextHolder.getLocale());
        return new ErrorMessage(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
    }
}
0
Zealot