Gestion des exceptions du service Spring Boot REST

173

J'essaye de mettre en place un serveur de services REST à grande échelle. Nous utilisons Spring Boot 1.2.1 Spring 4.1.5 et Java 8. Nos contrôleurs implémentent @RestController et les annotations standard @RequestMapping.

Mon problème est que Spring Boot configure une redirection par défaut pour les exceptions de contrôleur à /error. À partir de la documentation:

Spring Boot fournit un mappage / error par défaut qui gère toutes les erreurs de manière sensible, et il est enregistré en tant que page d'erreur «globale» dans le conteneur de servlet.

Venant d'années à écrire des applications REST avec Node.js, c'est pour moi tout sauf raisonnable. Toute exception générée par un point de terminaison de service doit renvoyer dans la réponse. Je ne peux pas comprendre pourquoi vous enverriez une redirection vers ce qui est probablement un consommateur Angular ou JQuery SPA qui recherche uniquement une réponse et ne peut ou ne prendra aucune action sur une redirection.

Ce que je veux faire, c'est configurer un gestionnaire d'erreurs global qui peut prendre n'importe quelle exception - soit délibérément levé à partir d'une méthode de mappage de demande, soit généré automatiquement par Spring (404 si aucune méthode de gestionnaire n'est trouvée pour la signature du chemin de la demande), et retourner un réponse d'erreur formatée standard (400, 500, 503, 404) au client sans aucune redirection MVC. Plus précisément, nous allons prendre l'erreur, la consigner sur NoSQL avec un UUID, puis retourner au client le bon code d'erreur HTTP avec l'UUID de l'entrée de journal dans le corps JSON.

Les documents ont été vagues sur la façon de procéder. Il me semble que vous devez créer votre propre implémentation ErrorController ou utiliser ControllerAdvice d'une manière ou d'une autre, mais tous les exemples que j'ai vus incluent toujours la transmission de la réponse à une sorte de mappage d'erreur, ce qui n'aide pas. D'autres exemples suggèrent que vous devez lister tous les types d'exceptions que vous souhaitez gérer au lieu de simplement lister "Throwable" et obtenir tout.

Quelqu'un peut-il me dire ce que j'ai manqué ou m'indiquer la bonne direction sur la façon de le faire sans suggérer la chaîne avec laquelle Node.js serait plus facile à gérer?

ogradyjd
la source
6
Le client ne reçoit jamais réellement de redirection. La redirection est gérée en interne par le conteneur de servlet (par exemple Tomcat).
OrangeDog
1
La suppression des annotations @ResponseStatus sur mes gestionnaires d'exceptions était ce dont j'avais besoin; voir stackoverflow.com/questions/35563968/…
pmorken

Réponses:

131

Nouvelle réponse (2016-04-20)

Utilisation de Spring Boot 1.3.1.RELEASE

Nouvelle étape 1 - Il est facile et moins intrusif d'ajouter les propriétés suivantes à l'application.properties:

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

Beaucoup plus facile que de modifier l'instance DispatcherServlet existante (comme ci-dessous)! - JO '

Si vous travaillez avec une application RESTful complète, il est très important de désactiver le mappage automatique des ressources statiques car si vous utilisez la configuration par défaut de Spring Boot pour gérer les ressources statiques, le gestionnaire de ressources traitera la demande (elle est commandée en dernier et mappée à / ** ce qui signifie qu'il récupère toutes les demandes qui n'ont été gérées par aucun autre gestionnaire de l'application), de sorte que le servlet du répartiteur n'a pas la possibilité de lever une exception.


Nouvelle réponse (2015-12-04)

Utilisation de Spring Boot 1.2.7.RELEASE

Nouvelle étape 1 - J'ai trouvé une manière beaucoup moins intrusive de définir le drapeau "throExceptionIfNoHandlerFound". Remplacez le code de remplacement DispatcherServlet ci-dessous (étape 1) par celui-ci dans la classe d'initialisation de votre application:

@ComponentScan()
@EnableAutoConfiguration
public class MyApplication extends SpringBootServletInitializer {
    private static Logger LOG = LoggerFactory.getLogger(MyApplication.class);
    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(MyApplication.class, args);
        DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
    }

Dans ce cas, nous définissons l'indicateur sur le DispatcherServlet existant, qui préserve toute configuration automatique par le framework Spring Boot.

Une dernière chose que j'ai trouvée - l'annotation @EnableWebMvc est mortelle pour Spring Boot. Oui, cette annotation permet, par exemple, de pouvoir intercepter toutes les exceptions du contrôleur comme décrit ci-dessous, mais elle élimine également BEAUCOUP de la configuration automatique utile que Spring Boot fournirait normalement. Utilisez cette annotation avec une extrême prudence lorsque vous utilisez Spring Boot.


Réponse originale:

Après beaucoup plus de recherches et de suivi des solutions publiées ici (merci pour l'aide!) Et pas une petite quantité de traçage d'exécution dans le code Spring, j'ai finalement trouvé une configuration qui gérera toutes les exceptions (pas les erreurs, mais lisez la suite) dont 404.

Étape 1 - dites à SpringBoot d'arrêter d'utiliser MVC pour les situations de «gestionnaire introuvable». Nous voulons que Spring lève une exception au lieu de renvoyer au client une redirection de vue vers "/ error". Pour ce faire, vous devez avoir une entrée dans l'une de vos classes de configuration:

// NEW CODE ABOVE REPLACES THIS! (2015-12-04)
@Configuration
public class MyAppConfig {
    @Bean  // Magic entry 
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet ds = new DispatcherServlet();
        ds.setThrowExceptionIfNoHandlerFound(true);
        return ds;
    }
}

L'inconvénient est qu'il remplace le servlet du répartiteur par défaut. Cela n'a pas encore été un problème pour nous, sans effets secondaires ni problèmes d'exécution. Si vous voulez faire autre chose avec le servlet du répartiteur pour d'autres raisons, c'est ici que vous devez le faire.

Étape 2 - Maintenant que spring boot lèvera une exception lorsqu'aucun gestionnaire n'est trouvé, cette exception peut être gérée avec n'importe quelle autre dans un gestionnaire d'exceptions unifié:

@EnableWebMvc
@ControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Throwable.class)
    @ResponseBody
    ResponseEntity<Object> handleControllerException(HttpServletRequest req, Throwable ex) {
        ErrorResponse errorResponse = new ErrorResponse(ex);
        if(ex instanceof ServiceException) {
            errorResponse.setDetails(((ServiceException)ex).getDetails());
        }
        if(ex instanceof ServiceHttpException) {
            return new ResponseEntity<Object>(errorResponse,((ServiceHttpException)ex).getStatus());
        } else {
            return new ResponseEntity<Object>(errorResponse,HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @Override
    protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        Map<String,String> responseBody = new HashMap<>();
        responseBody.put("path",request.getContextPath());
        responseBody.put("message","The URL you have reached is not in service at this time (404).");
        return new ResponseEntity<Object>(responseBody,HttpStatus.NOT_FOUND);
    }
    ...
}

Gardez à l'esprit que je pense que l'annotation "@EnableWebMvc" est importante ici. Il semble que rien de tout cela ne fonctionne sans lui. Et c'est tout - votre application Spring Boot détecte désormais toutes les exceptions, y compris les 404, dans la classe de gestionnaire ci-dessus et vous pouvez les utiliser à votre guise.

Un dernier point - il ne semble pas y avoir de moyen d'obtenir cela pour attraper les erreurs lancées. J'ai une idée farfelue d'utiliser des aspects pour détecter les erreurs et les transformer en exceptions que le code ci-dessus peut ensuite traiter, mais je n'ai pas encore eu le temps d'essayer de l'implémenter. J'espère que cela aide quelqu'un.

Tous les commentaires / corrections / améliorations seront appréciés.

ogradyjd
la source
au lieu de créer un nouveau bean servlet du répartiteur, vous pouvez retourner l'indicateur dans un post-processeur: YourClass implements BeanPostProcessor {... `Public Object postProcessBeforeInitialization (Object bean, String beanName) jette BeansException {if (bean instanceof DispatcherServlet) {// sinon nous obtenir un 404 avant que notre gestionnaire d'exceptions ne démarre ((DispatcherServlet) bean) .setThrowExceptionIfNoHandlerFound (true); } return bean; } Objet public postProcessAfterInitialization (bean objet, String beanName) jette BeansException {return bean; }
wwadge
1
J'ai ce problème mais la personnalisation du DispatcherServlet ne fonctionne pas pour moi. Y a-t-il une magie supplémentaire requise pour que Boot utilise ce bean et cette configuration supplémentaires?
IanGilham
3
@IanGilham Je ne fais pas non plus fonctionner cela avec Spring Boot 1.2.7. Je n'obtiens même aucune @ExceptionHandlerméthode appelée lorsque je la place dans la @ControllerAdviceclasse bien qu'elles fonctionnent correctement si elles sont placées dans la @RestControllerclasse. @EnableWebMvcest sur la classe @ControllerAdviceet @Configuration(j'ai testé chaque combinaison). Une idée ou un exemple concret? // @Andy Wilkinson
FrVaBe
1
Quiconque lit cette question et réponse devrait jeter un œil au problème SpringBoot correspondant sur github .
FrVaBe
1
Pas sûr @agpt. J'ai un projet interne que je peux déplacer jusqu'à 1.3.0 et voir quel est l'effet sur ma configuration et vous faire savoir ce que je trouve.
ogradyjd
41

Avec Spring Boot 1.4+, de nouvelles classes cool pour faciliter la gestion des exceptions ont été ajoutées, ce qui aide à supprimer le code standard.

Un nouveau @RestControllerAdviceest fourni pour la gestion des exceptions, c'est une combinaison de @ControllerAdviceet @ResponseBody. Vous pouvez supprimer le @ResponseBodysur la @ExceptionHandlerméthode lorsque vous utilisez cette nouvelle annotation.

c'est à dire

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

    @ExceptionHandler(value = { Exception.class })
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ApiErrorResponse unknownException(Exception ex, WebRequest req) {
        return new ApiErrorResponse(...);
    }
}

Pour gérer les erreurs 404, l'ajout d' @EnableWebMvcannotation et de ce qui suit à application.properties était suffisant:
spring.mvc.throw-exception-if-no-handler-found=true

Vous pouvez trouver et jouer avec les sources ici:
https://github.com/magiccrafter/spring-boot-exception-handling

magiccrafter
la source
7
C'est vraiment utile, merci. Mais je n'ai pas compris pourquoi nous devons `@EnableWebMvc` avec `spring.mvc.throw-exception-if-no-handler-found = true`. Mon attente était de gérer toutes les exceptions via @RestControllerAdvicesans configuration supplémentaire. Qu'est-ce que j'oublie ici?
fiskra
28

Je pense que ResponseEntityExceptionHandlerrépond à vos exigences. Un exemple de code pour HTTP 400:

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

  @ResponseStatus(value = HttpStatus.BAD_REQUEST)
  @ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentNotValidException.class,
      HttpRequestMethodNotSupportedException.class})
  public ResponseEntity<Object> badRequest(HttpServletRequest req, Exception exception) {
    // ...
  }
}

Vous pouvez consulter ce post

Efe Kahraman
la source
6
J'ai déjà vu ce code, et après l'avoir implémenté, la classe a intercepté les exceptions soulevées dans les méthodes de mappage de requêtes de contrôleur. Cela n'attrape toujours pas les erreurs 404, qui sont gérées dans la méthode ResourceHttpRequestHandler.handleRequest ou, si l'annotation @EnableWebMvc est utilisée, dans DispatcherServlet.noHandlerFound. Nous voulons gérer toute erreur, y compris les 404, mais la dernière version de Spring Boot semble être incroyablement obtuse sur la façon de le faire.
ogradyjd
J'ai écrit de la même manière pour gérer HttpRequestMethodNotSupportedExceptionet installer le même fichier jar dans plusieurs micro-services, dans certains cas, nous devons répondre au nom d'alias du micro-service dans la réponse. existe-t-il un moyen d'obtenir le nom du micro-service / le nom du contrôleur sous-jacent? Je sais que je HandlerMethodfournirai le nom de la méthode java d'où l'exception est originaire. Mais ici, aucune des méthodes n'a reçu la demande, donc HandlerMethodne sera pas initialisée. Alors, y a-t-il une solution pour résoudre ce problème?
Paramesh Korrakuti
Le conseil du contrôleur est une bonne approche, mais rappelez-vous toujours que les exceptions ne font pas partie du flux qu'elles doivent se produire dans des cas exceptionnels!
JorgeTovar
17

Bien que ce soit une question plus ancienne, je voudrais partager mes réflexions à ce sujet. J'espère que cela sera utile à certains d'entre vous.

Je construis actuellement une API REST qui utilise Spring Boot 1.5.2.RELEASE avec Spring Framework 4.3.7.RELEASE. J'utilise l'approche Java Config (par opposition à la configuration XML). De plus, mon projet utilise un mécanisme global de gestion des exceptions utilisant l' @RestControllerAdviceannotation (voir plus loin ci-dessous).

Mon projet a les mêmes exigences que le vôtre: je veux que mon API REST renvoie un HTTP 404 Not Foundavec une charge utile JSON qui l'accompagne dans la réponse HTTP au client API lorsqu'il tente d'envoyer une requête à une URL qui n'existe pas. Dans mon cas, la charge utile JSON ressemble à ceci (qui diffère clairement de la valeur par défaut de Spring Boot, btw.):

{
    "code": 1000,
    "message": "No handler found for your request.",
    "timestamp": "2017-11-20T02:40:57.628Z"
}

Je l'ai finalement fait fonctionner. Voici les principales tâches que vous devez effectuer en bref:

  • Assurez-vous que le NoHandlerFoundExceptionest levé si les clients API appellent des URL pour lesquelles aucune méthode de gestionnaire n'existe (voir l'étape 1 ci-dessous).
  • Créez une classe d'erreur personnalisée (dans mon cas ApiError) qui contient toutes les données qui doivent être renvoyées au client API (voir étape 2).
  • Créez un gestionnaire d'exceptions qui réagit sur le NoHandlerFoundException et renvoie un message d'erreur approprié au client API (voir étape 3).
  • Écrivez un test pour cela et assurez-vous que cela fonctionne (voir l'étape 4).

Ok, maintenant aux détails:

Étape 1: Configurez application.properties

J'ai dû ajouter les deux paramètres de configuration suivants au application.propertiesfichier du projet :

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

Cela garantit que le NoHandlerFoundExceptionest levé dans les cas où un client tente d'accéder à une URL pour laquelle il n'existe aucune méthode de contrôleur qui serait en mesure de gérer la demande.

Étape 2: créer une classe pour les erreurs d'API

J'ai fait un cours similaire à celui suggéré dans cet article sur le blog d'Eugen Paraschiv. Cette classe représente une erreur d'API. Ces informations sont envoyées au client dans le corps de la réponse HTTP en cas d'erreur.

public class ApiError {

    private int code;
    private String message;
    private Instant timestamp;

    public ApiError(int code, String message) {
        this.code = code;
        this.message = message;
        this.timestamp = Instant.now();
    }

    public ApiError(int code, String message, Instant timestamp) {
        this.code = code;
        this.message = message;
        this.timestamp = timestamp;
    }

    // Getters and setters here...
}

Étape 3: créer / configurer un gestionnaire d'exceptions global

J'utilise la classe suivante pour gérer les exceptions (pour plus de simplicité, j'ai supprimé les instructions d'importation, le code de journalisation et d'autres morceaux de code non pertinents):

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ApiError noHandlerFoundException(
            NoHandlerFoundException ex) {

        int code = 1000;
        String message = "No handler found for your request.";
        return new ApiError(code, message);
    }

    // More exception handlers here ...
}

Étape 4: rédigez un test

Je veux m'assurer que l'API renvoie toujours les bons messages d'erreur au client appelant, même en cas d'échec. Ainsi, j'ai écrit un test comme celui-ci:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SprintBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ActiveProfiles("dev")
public class GlobalExceptionHandlerIntegrationTest {

    public static final String ISO8601_DATE_REGEX =
        "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$";

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithMockUser(roles = "DEVICE_SCAN_HOSTS")
    public void invalidUrl_returnsHttp404() throws Exception {
        RequestBuilder requestBuilder = getGetRequestBuilder("/does-not-exist");
        mockMvc.perform(requestBuilder)
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.code", is(1000)))
            .andExpect(jsonPath("$.message", is("No handler found for your request.")))
            .andExpect(jsonPath("$.timestamp", RegexMatcher.matchesRegex(ISO8601_DATE_REGEX)));
    }

    private RequestBuilder getGetRequestBuilder(String url) {
        return MockMvcRequestBuilders
            .get(url)
            .accept(MediaType.APPLICATION_JSON);
    }

L' @ActiveProfiles("dev")annotation peut être laissée de côté. Je l'utilise uniquement car je travaille avec différents profils. Il RegexMatchers'agit d'un matcher Hamcrest personnalisé que j'utilise pour mieux gérer les champs d'horodatage. Voici le code (je l'ai trouvé ici ):

public class RegexMatcher extends TypeSafeMatcher<String> {

    private final String regex;

    public RegexMatcher(final String regex) {
        this.regex = regex;
    }

    @Override
    public void describeTo(final Description description) {
        description.appendText("matches regular expression=`" + regex + "`");
    }

    @Override
    public boolean matchesSafely(final String string) {
        return string.matches(regex);
    }

    // Matcher method you can call on this matcher class
    public static RegexMatcher matchesRegex(final String string) {
        return new RegexMatcher(regex);
    }
}

Quelques notes supplémentaires de mon côté:

  • Dans de nombreux autres articles sur StackOverflow, les gens ont suggéré de définir l' @EnableWebMvcannotation. Ce n'était pas nécessaire dans mon cas.
  • Cette approche fonctionne bien avec MockMvc (voir test ci-dessus).
André Gasser
la source
Cela a résolu le problème pour moi. Juste pour ajouter, il me manquait l'annotation @ RestControllerAdvice, alors je l'ai ajoutée avec l'annotation @ ControllerAdvice afin qu'elle gère tout et cela a fait l'affaire.
PGMacDesign
13

Et ce code? J'utilise un mappage de demande de secours pour attraper les erreurs 404.

@Controller
@ControllerAdvice
public class ExceptionHandlerController {

    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        //If exception has a ResponseStatus annotation then use its response code
        ResponseStatus responseStatusAnnotation = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);

        return buildModelAndViewErrorPage(request, response, ex, responseStatusAnnotation != null ? responseStatusAnnotation.value() : HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @RequestMapping("*")
    public ModelAndView fallbackHandler(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return buildModelAndViewErrorPage(request, response, null, HttpStatus.NOT_FOUND);
    }

    private ModelAndView buildModelAndViewErrorPage(HttpServletRequest request, HttpServletResponse response, Exception ex, HttpStatus httpStatus) {
        response.setStatus(httpStatus.value());

        ModelAndView mav = new ModelAndView("error.html");
        if (ex != null) {
            mav.addObject("title", ex);
        }
        mav.addObject("content", request.getRequestURL());
        return mav;
    }

}
Ludovic Martin
la source
6

Par défaut, Spring Boot donne à json les détails de l'erreur.

curl -v localhost:8080/greet | json_pp
[...]
< HTTP/1.1 400 Bad Request
[...]
{
   "timestamp" : 1413313361387,
   "exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
   "status" : 400,
   "error" : "Bad Request",
   "path" : "/greet",
   "message" : "Required String parameter 'name' is not present"
}

Cela fonctionne également pour tous les types d'erreurs de mappage de demande. Consultez cet article http://www.jayway.com/2014/10/19/spring-boot-error-responses/

Si vous souhaitez créer un journal sur NoSQL. Vous pouvez créer @ControllerAdvice où vous le consigneriez, puis relancer l'exception. Il y a un exemple dans la documentation https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

PeintRouge
la source
Le DispatcherServlet par défaut est codé en dur pour effectuer la redirection avec MVC plutôt que de lever une exception lorsqu'une demande de mappage inexistant est reçue - à moins que vous ne définissiez l'indicateur comme je l'ai fait dans le post ci-dessus.
ogradyjd
En outre, la raison pour laquelle nous avons implémenté la classe ResponseEntityExceptionHandler est que nous pourrions contrôler le format de la sortie et enregistrer les traces de la pile d'erreurs vers une solution NoSQL, puis envoyer un message d'erreur sécurisé pour le client.
ogradyjd
6

@RestControllerAdvice est une nouvelle fonctionnalité de Spring Framework 4.3 pour gérer l'exception avec RestfulApi par une solution de préoccupation transversale:

 package com.khan.vaquar.exception;

import javax.servlet.http.HttpServletRequest;

import org.owasp.esapi.errors.IntrusionException;
import org.owasp.esapi.errors.ValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.khan.vaquar.domain.ErrorResponse;

/**
 * Handles exceptions raised through requests to spring controllers.
 **/
@RestControllerAdvice
public class RestExceptionHandler {

    private static final String TOKEN_ID = "tokenId";

    private static final Logger log = LoggerFactory.getLogger(RestExceptionHandler.class);

    /**
     * Handles InstructionExceptions from the rest controller.
     * 
     * @param e IntrusionException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = IntrusionException.class)
    public ErrorResponse handleIntrusionException(HttpServletRequest request, IntrusionException e) {       
        log.warn(e.getLogMessage(), e);
        return this.handleValidationException(request, new ValidationException(e.getUserMessage(), e.getLogMessage()));
    }

    /**
     * Handles ValidationExceptions from the rest controller.
     * 
     * @param e ValidationException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = ValidationException.class)
    public ErrorResponse handleValidationException(HttpServletRequest request, ValidationException e) {     
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);

        if (e.getUserMessage().contains("Token ID")) {
            tokenId = "<OMITTED>";
        }

        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(),
                                    e.getUserMessage());
    }

    /**
     * Handles JsonProcessingExceptions from the rest controller.
     * 
     * @param e JsonProcessingException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = JsonProcessingException.class)
    public ErrorResponse handleJsonProcessingException(HttpServletRequest request, JsonProcessingException e) {     
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(),
                                    e.getOriginalMessage());
    }

    /**
     * Handles IllegalArgumentExceptions from the rest controller.
     * 
     * @param e IllegalArgumentException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = IllegalArgumentException.class)
    public ErrorResponse handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(), 
                                    e.getMessage());
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = UnsupportedOperationException.class)
    public ErrorResponse handleUnsupportedOperationException(HttpServletRequest request, UnsupportedOperationException e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(), 
                                    e.getMessage());
    }

    /**
     * Handles MissingServletRequestParameterExceptions from the rest controller.
     * 
     * @param e MissingServletRequestParameterException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ErrorResponse handleMissingServletRequestParameterException( HttpServletRequest request, 
                                                                        MissingServletRequestParameterException e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(), 
                                    e.getMessage());
    }

    /**
     * Handles NoHandlerFoundExceptions from the rest controller.
     * 
     * @param e NoHandlerFoundException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(value = NoHandlerFoundException.class)
    public ErrorResponse handleNoHandlerFoundException(HttpServletRequest request, NoHandlerFoundException e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.NOT_FOUND.value(), 
                                    e.getClass().getSimpleName(), 
                                    "The resource " + e.getRequestURL() + " is unavailable");
    }

    /**
     * Handles all remaining exceptions from the rest controller.
     * 
     * This acts as a catch-all for any exceptions not handled by previous exception handlers.
     * 
     * @param e Exception
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = Exception.class)
    public ErrorResponse handleException(HttpServletRequest request, Exception e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.error(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.INTERNAL_SERVER_ERROR.value(), 
                                    e.getClass().getSimpleName(), 
                                    "An internal error occurred");
    }   

}
vaquar khan
la source
3

Pour les contrôleurs REST, je recommanderais d'utiliser Zalando Problem Spring Web.

https://github.com/zalando/problem-spring-web

Si Spring Boot vise à intégrer une configuration automatique, cette bibliothèque en fait plus pour la gestion des exceptions. Il vous suffit d'ajouter la dépendance:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>problem-spring-web</artifactId>
    <version>LATEST</version>
</dependency>

Et puis définissez un ou plusieurs traits de conseil pour vos exceptions (ou utilisez ceux fournis par défaut)

public interface NotAcceptableAdviceTrait extends AdviceTrait {

    @ExceptionHandler
    default ResponseEntity<Problem> handleMediaTypeNotAcceptable(
            final HttpMediaTypeNotAcceptableException exception,
            final NativeWebRequest request) {
        return Responses.create(Status.NOT_ACCEPTABLE, exception, request);
    }

}

Ensuite, vous pouvez définir le conseil du contrôleur pour la gestion des exceptions comme:

@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {

}
Jean Valjean
la source
2

Pour les personnes qui souhaitent répondre en fonction du code d'état http, vous pouvez utiliser la ErrorControllerméthode:

@Controller
public class CustomErrorController extends BasicErrorController {

    public CustomErrorController(ServerProperties serverProperties) {
        super(new DefaultErrorAttributes(), serverProperties.getError());
    }

    @Override
    public ResponseEntity error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)){
            return ResponseEntity.status(status).body(ResponseBean.SERVER_ERROR);
        }else if (status.equals(HttpStatus.BAD_REQUEST)){
            return ResponseEntity.status(status).body(ResponseBean.BAD_REQUEST);
        }
        return super.error(request);
    }
}

Le ResponseBeanvoici mon pojo personnalisé pour la réponse.

Lym Zoy
la source
0

Solution avec dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);et a @EnableWebMvc @ControllerAdvice fonctionné pour moi avec Spring Boot 1.3.1, alors que ne fonctionnait pas sur 1.2.7

Dennis R
la source