Gestione delle eccezioni del servizio REST di Spring Boot


173

Sto cercando di configurare un server di servizi REST su larga scala. Stiamo utilizzando Spring Boot 1.2.1 Spring 4.1.5 e Java 8. I nostri controller stanno implementando le annotazioni @RestController e standard @RequestMapping.

Il mio problema è che Spring Boot imposta un reindirizzamento predefinito per le eccezioni del controller /error. Dai documenti:

Spring Boot fornisce un mapping / error per impostazione predefinita che gestisce tutti gli errori in modo ragionevole ed è registrato come pagina di errore "globale" nel contenitore servlet.

Venendo da anni a scrivere applicazioni REST con Node.js, per me è tutt'altro che ragionevole. Qualsiasi eccezione generata da un endpoint del servizio deve restituire la risposta. Non riesco a capire perché invii un reindirizzamento a quello che è molto probabilmente un consumatore di Angular o JQuery SPA che sta solo cercando una risposta e non può o non intende intraprendere alcuna azione su un reindirizzamento.

Quello che voglio fare è impostare un gestore di errori globale che possa prendere qualsiasi eccezione - lanciato intenzionalmente da un metodo di mappatura di richiesta o generato automaticamente da Spring (404 se non viene trovato alcun metodo di gestore per la firma del percorso di richiesta) e restituisce un risposta di errore formattata standard (400, 500, 503, 404) al client senza reindirizzamenti MVC. In particolare, prenderemo l'errore, lo registreremo su NoSQL con un UUID, quindi restituiremo al client il codice di errore HTTP corretto con l'UUID della voce di registro nel corpo JSON.

I documenti sono stati vaghi su come farlo. Mi sembra che tu debba creare la tua implementazione ErrorController o utilizzare ControllerAdvice in qualche modo, ma tutti gli esempi che ho visto includono ancora l'inoltro della risposta a un qualche tipo di mappatura degli errori, che non aiuta. Altri esempi suggeriscono che dovresti elencare tutti i tipi di eccezione che desideri gestire anziché semplicemente elencare "Gettabile" e ottenere tutto.

Qualcuno può dirmi cosa mi sono perso o indicarmi la giusta direzione su come farlo senza suggerire la catena con cui Node.js sarebbe più facile da gestire?


6
Al client non viene mai effettivamente inviato un reindirizzamento. Il reindirizzamento viene gestito internamente dal contenitore servlet (ad es. Tomcat).
OrangeDog,

1
La rimozione delle annotazioni @ResponseStatus sui gestori delle eccezioni era ciò di cui avevo bisogno; vedi stackoverflow.com/questions/35563968/...
pmorken

Risposte:


131

Nuova risposta (20-04-2016)

Utilizzando Spring Boot 1.3.1.RELEASE

Nuovo passaggio 1: aggiungere le seguenti proprietà a application.properties è semplice e meno invadente:

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

Molto più semplice che modificare l'istanza DispatcherServlet esistente (come di seguito)! - JO '

Se si lavora con un'applicazione RESTful completa, è molto importante disabilitare il mapping automatico delle risorse statiche poiché se si utilizza la configurazione predefinita di Spring Boot per la gestione delle risorse statiche, il gestore delle risorse gestirà la richiesta (è stata ordinata per ultima e mappata su / ** il che significa che raccoglie tutte le richieste che non sono state gestite da nessun altro gestore nell'applicazione) in modo che il servlet dispatcher non abbia la possibilità di lanciare un'eccezione.


Nuova risposta (04-12-2015)

Utilizzando Spring Boot 1.2.7.RELEASE

Nuovo passaggio 1: ho trovato un modo molto meno invadente di impostare il flag "throExceptionIfNoHandlerFound". Sostituisci il codice di sostituzione DispatcherServlet di seguito (Passaggio 1) con questo nella classe di inizializzazione dell'applicazione:

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

In questo caso, stiamo impostando il flag sul DispatcherServlet esistente, che preserva qualsiasi configurazione automatica da parte del framework Spring Boot.

Un'altra cosa che ho trovato: l'annotazione @EnableWebMvc è mortale per Spring Boot. Sì, quell'annotazione consente cose come essere in grado di catturare tutte le eccezioni del controller come descritto di seguito, ma uccide anche MOLTE utili configurazioni automatiche che Spring Boot fornirebbe normalmente. Usa quell'annotazione con estrema cautela quando usi Spring Boot.


Risposta originale:

Dopo molte più ricerche e seguito sulle soluzioni pubblicate qui (grazie per l'aiuto!) E nessuna piccola quantità di traccia del runtime nel codice Spring, ho finalmente trovato una configurazione che gestirà tutte le Eccezioni (non Errori, ma continua a leggere) di cui 404s.

Passaggio 1: dire a SpringBoot di smettere di usare MVC per situazioni "gestore non trovato". Vogliamo che Spring generi un'eccezione invece di restituire al client una vista reindirizzata a "/ errore". Per fare ciò, devi avere una voce in una delle tue classi di configurazione:

// 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'aspetto negativo di questo è che sostituisce il servlet predefinito del dispatcher. Questo non è stato ancora un problema per noi, senza effetti collaterali o problemi di esecuzione. Se hai intenzione di fare qualcos'altro con il servlet dispatcher per altri motivi, questo è il posto giusto per farli.

Passaggio 2: ora che Spring Boot genererà un'eccezione quando non viene trovato alcun gestore, tale eccezione può essere gestita con qualsiasi altro in un gestore unificato delle eccezioni:

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

Tieni presente che penso che l'annotazione "@EnableWebMvc" sia significativa qui. Sembra che niente di tutto questo funzioni senza di essa. E il gioco è fatto: la tua app di avvio di Spring ora rileverà tutte le eccezioni, tra cui 404, nella classe di gestori di cui sopra e puoi farcela a tuo piacimento.

Un ultimo punto: non sembra esserci un modo per far sì che questo possa catturare gli errori generati. Ho una strana idea di usare gli aspetti per rilevare errori e trasformarli in Eccezioni che il codice sopra può quindi affrontare, ma non ho ancora avuto il tempo di provare a implementarlo. Spero che questo aiuti qualcuno.

Eventuali commenti / correzioni / miglioramenti saranno apprezzati.


invece di creare un nuovo bean servlet dispatcher puoi capovolgere il flag in un post processor: YourClass implementa BeanPostProcessor {... `public Object postProcessBeforeInitialization (Object bean, String beanName) genera BeansException {if (bean instanceof DispatcherServlet) {// altrimenti ottenere un 404 prima che il gestore delle eccezioni entri nel bean ((DispatcherServlet)) .setThrowExceptionIfNoHandlerFound (true); } bean di ritorno; } oggetto pubblico postProcessAfterInitialization (Object bean, String beanName) genera BeansException {return bean; }
wwadge,

1
Ho questo problema ma la personalizzazione di DispatcherServlet non funziona per me. C'è qualche magia aggiuntiva richiesta affinché Boot usi questo bean e config in più?
IanGilham,

3
@IanGilham Anche io non riesco a farlo funzionare con Spring Boot 1.2.7. Non riesco nemmeno a @ExceptionHandlerchiamare alcun metodo quando lo inserisco nella @ControllerAdviceclasse sebbene funzionino correttamente se inseriti nella @RestControllerclasse. @EnableWebMvcè nella classe @ControllerAdvicee @Configuration(ho provato ogni combinazione). Qualche idea o esempio funzionante? // @Andy Wilkinson
FrVaBe,

1
Chiunque legga questa domanda e risposta dovrebbe dare un'occhiata al corrispondente Problema SpringBoot su github .
FrVaBe,

1
Non sono sicuro @agpt. Ho un progetto interno che posso spostare fino alla 1.3.0 e vedere qual è l'effetto sulla mia configurazione e farti sapere cosa trovo.
ogradyjd,

41

Con Spring Boot 1.4+ sono state aggiunte nuove interessanti classi per una più semplice gestione delle eccezioni che aiuta a rimuovere il codice del boilerplate.

Un nuovo @RestControllerAdviceè fornito per la gestione delle eccezioni, è una combinazione di @ControllerAdvicee @ResponseBody. È possibile rimuovere il @ResponseBodysul @ExceptionHandlermetodo quando utilizzare questa nuova annotazione.

vale a dire

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

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

Per gestire gli errori 404 con l'aggiunta di @EnableWebMvcannotazioni e quanto segue in application.properties è stato sufficiente:
spring.mvc.throw-exception-if-no-handler-found=true

Puoi trovare e giocare con le fonti qui:
https://github.com/magiccrafter/spring-boot-exception-handling


7
È davvero utile, grazie. Ma non ho capito perché abbiamo bisogno di `@EnableWebMvc` con `spring.mvc.throw-exception-if-no-handler-found = true`. La mia aspettativa era di gestire tutte le eccezioni @RestControllerAdvicesenza una configurazione aggiuntiva. Cosa mi sto perdendo qui?
fiskra,

28

Penso che ResponseEntityExceptionHandlersoddisfi le tue esigenze. Un esempio di codice per 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) {
    // ...
  }
}

Puoi controllare questo post


6
Ho già visto questo codice e, dopo averlo implementato, la classe ha rilevato eccezioni sollevate nei metodi di richiesta richieste del controller. Questo non rileva ancora 404 errori, che vengono gestiti nel metodo ResourceHttpRequestHandler.handleRequest o, se viene utilizzata l'annotazione @EnableWebMvc, in DispatcherServlet.noHandlerFound. Vogliamo gestire qualsiasi errore, compresi i 404, ma l'ultima versione di Spring Boot sembra essere incredibilmente ottusa su come farlo.
ogradyjd,

Ho scritto allo stesso modo per gestire HttpRequestMethodNotSupportedExceptione collegare lo stesso jar in più micro-servizi, per alcuni scopi aziendali dobbiamo rispondere alias di micro-servizio nella risposta. esiste un modo per ottenere il nome del micro-servizio sottostante / il nome del controller? So HandlerMethodche fornirà il nome del metodo java da cui proviene l'eccezione. Ma qui, nessuno dei metodi ha ricevuto la richiesta, quindi HandlerMethodnon verrà inizializzato. Quindi c'è qualche soluzione per risolvere questo?
Paramesh Korrakuti,

La consulenza del controller è un buon approccio, ma ricorda sempre che le eccezioni non fanno parte del flusso che devono verificarsi in casi eccezionali!
Jorge Tovar,

17

Sebbene questa sia una domanda più vecchia, vorrei condividere le mie opinioni su questo. Spero che sarà utile per alcuni di voi.

Attualmente sto creando un'API REST che utilizza Spring Boot 1.5.2.RELEASE con Spring Framework 4.3.7.RELEASE. Uso l'approccio Java Config (al contrario della configurazione XML). Inoltre, il mio progetto usa un meccanismo globale di gestione delle eccezioni usando l' @RestControllerAdviceannotazione (vedi più avanti sotto).

Il mio progetto ha gli stessi requisiti del tuo: voglio che la mia API REST restituisca un HTTP 404 Not Foundcon un payload JSON di accompagnamento nella risposta HTTP al client API quando tenta di inviare una richiesta a un URL che non esiste. Nel mio caso, il payload JSON è simile al seguente (che differisce nettamente dall'impostazione predefinita di Spring Boot, tra l'altro):

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

Finalmente l'ho fatto funzionare. Ecco le principali attività che devi svolgere in breve:

  • Assicurarsi che NoHandlerFoundExceptionvenga generato se i client API chiamano URL per i quali non esiste alcun metodo del gestore (vedere il passaggio 1 di seguito).
  • Crea una classe di errore personalizzata (nel mio caso ApiError ) che contenga tutti i dati che dovrebbero essere restituiti al client API (vedere il passaggio 2).
  • Creare un gestore eccezioni che reagisce sul NoHandlerFoundException e restituisce un messaggio di errore corretto al client API (vedere il passaggio 3).
  • Scrivi un test per questo e assicurati che funzioni (vedi passaggio 4).

Ok, ora passiamo ai dettagli:

Passaggio 1: configura application.properties

Ho dovuto aggiungere le seguenti due impostazioni di configurazione al application.propertiesfile del progetto :

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

Ciò si assicura, NoHandlerFoundExceptionviene generato nei casi in cui un client tenta di accedere a un URL per il quale non esiste alcun metodo del controller in grado di gestire la richiesta.

Passaggio 2: creare una classe per errori API

Ho fatto una lezione simile a quella suggerita in questo articolo sul blog di Eugen Paraschiv. Questa classe rappresenta un errore API. Queste informazioni vengono inviate al client nel corpo della risposta HTTP in caso di errore.

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

Passaggio 3: creare / configurare un gestore eccezioni globale

Uso la seguente classe per gestire le eccezioni (per semplicità, ho rimosso le istruzioni di importazione, il codice di registrazione e alcuni altri pezzi di codice non rilevanti):

@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 ...
}

Passaggio 4: scrivere un test

Voglio assicurarmi che l'API restituisca sempre i messaggi di errore corretti al client chiamante, anche in caso di errore. Quindi, ho scritto un test come questo:

@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")annotazione può essere lasciata da parte. Lo uso solo mentre lavoro con profili diversi. Il RegexMatcherè una consuetudine Hamcrest matcher io uso per migliori campi data e ora della maniglia. Ecco il codice (l'ho trovato qui ):

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

Alcune ulteriori note dalla mia parte:

  • In molti altri post su StackOverflow, le persone hanno suggerito di impostare l' @EnableWebMvcannotazione. Questo non era necessario nel mio caso.
  • Questo approccio funziona bene con MockMvc (vedi test sopra).

Questo ha risolto il problema per me. Solo per aggiungere, mi mancava l'annotazione @ RestControllerAdvice, quindi l'ho aggiunta insieme all'annotazione @ ControllerAdvice in modo che potesse gestire tutto e questo ha funzionato.
PGMacDesign

13

Che mi dici di questo codice? Uso una mappatura delle richieste di fallback per rilevare 404 errori.

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

}

6

Per impostazione predefinita, Spring Boot fornisce a json i dettagli dell'errore.

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"
}

Funziona anche con tutti i tipi di errori di mappatura delle richieste. Controlla questo articolo http://www.jayway.com/2014/10/19/spring-boot-error-responses/

Se si desidera creare, registrarlo su NoSQL. Puoi creare @ControllerAdvice nel punto in cui lo registreresti e quindi rigettare l'eccezione. C'è un esempio nella documentazione https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc


DispatcherServlet predefinito è hardcoded per eseguire il reindirizzamento con MVC anziché generare un'eccezione quando viene ricevuta una richiesta per un mapping inesistente, a meno che non si imposti il ​​flag come ho fatto nel post precedente.
ogradyjd,

Inoltre, il motivo per cui abbiamo implementato la classe ResponseEntityExceptionHandler è che abbiamo potuto controllare il formato dell'output e registrare le tracce dello stack di errori in una soluzione NoSQL e quindi inviare un messaggio di errore sicuro per il client.
ogradyjd,

6

@RestControllerAdvice è una nuova funzionalità di Spring Framework 4.3 per la gestione delle eccezioni con RestfulApi mediante una soluzione di preoccupazione trasversale:

 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");
    }   

}

3

Per i controller REST, consiglierei di usare Zalando Problem Spring Web.

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

Se Spring Boot mira a incorporare alcune configurazioni automatiche, questa libreria fa di più per la gestione delle eccezioni. Devi solo aggiungere la dipendenza:

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

E quindi definire uno o più tratti di consulenza per le tue eccezioni (o utilizzare quelli forniti per impostazione predefinita)

public interface NotAcceptableAdviceTrait extends AdviceTrait {

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

}

Quindi è possibile definire i consigli del controller per la gestione delle eccezioni come:

@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {

}

2

Per le persone che desiderano rispondere in base al codice di stato http, è possibile utilizzare il ErrorControllermodo:

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

Il ResponseBeanqui è la mia POJO personalizzato per la risposta.


0

Soluzione con dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);e ha @EnableWebMvc @ControllerAdvice funzionato per me con Spring Boot 1.3.1, mentre non funzionava su 1.2.7

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.