Behandlung von Spring Boot REST-Dienstausnahmen

172

Ich versuche, einen großen REST-Serviceserver einzurichten. Wir verwenden Spring Boot 1.2.1 Spring 4.1.5 und Java 8. Unsere Controller implementieren @RestController und die standardmäßigen @ RequestMapping-Annotationen.

Mein Problem ist, dass Spring Boot eine Standardumleitung für Controller-Ausnahmen zu einrichtet /error. Aus den Dokumenten:

Spring Boot bietet standardmäßig eine / error-Zuordnung, die alle Fehler auf sinnvolle Weise behandelt und als 'globale' Fehlerseite im Servlet-Container registriert ist.

Aus jahrelangen REST-Anwendungen mit Node.js zu schreiben, ist für mich alles andere als sinnvoll. Jede Ausnahme, die ein Service-Endpunkt generiert, sollte in der Antwort zurückgegeben werden. Ich kann nicht verstehen, warum Sie eine Weiterleitung an einen Angular- oder JQuery SPA-Verbraucher senden, der nur nach einer Antwort sucht und bei einer Weiterleitung keine Maßnahmen ergreifen kann oder will.

Ich möchte einen globalen Fehlerhandler einrichten, der jede Ausnahme annehmen kann - entweder absichtlich von einer Anforderungszuordnungsmethode ausgelöst oder von Spring automatisch generiert (404, wenn keine Handlermethode für die Anforderungspfadsignatur gefunden wird), und a zurückgeben Standardformatierte Fehlerantwort (400, 500, 503, 404) an den Client ohne MVC-Umleitungen. Insbesondere nehmen wir den Fehler, protokollieren ihn mit einer UUID bei NoSQL und geben dann den richtigen HTTP-Fehlercode mit der UUID des Protokolleintrags im JSON-Body an den Client zurück.

Die Dokumente waren vage, wie das geht. Es scheint mir, dass Sie entweder Ihre eigene ErrorController- Implementierung erstellen oder ControllerAdvice auf irgendeine Weise verwenden müssen, aber alle Beispiele, die ich gesehen habe, beinhalten immer noch das Weiterleiten der Antwort auf eine Art Fehlerzuordnung, was nicht hilft. Andere Beispiele legen nahe, dass Sie jeden Ausnahmetyp auflisten müssen, den Sie behandeln möchten, anstatt nur "Throwable" aufzulisten und alles abzurufen.

Kann mir jemand sagen, was ich verpasst habe, oder mich in die richtige Richtung weisen, wie dies zu tun ist, ohne die Kette vorzuschlagen, mit der Node.js einfacher umzugehen wäre?

ogradyjd
quelle
6
Dem Client wird niemals eine Weiterleitung gesendet. Die Umleitung wird intern vom Servlet-Container (z. B. Tomcat) ausgeführt.
OrangeDog
1
Das Entfernen der @ ResponseStatus-Annotationen in meinen Ausnahmebehandlungsroutinen war das, was ich brauchte. siehe stackoverflow.com/questions/35563968/…
pmorken

Antworten:

131

Neue Antwort (20.04.2016)

Verwenden von Spring Boot 1.3.1.RELEASE

Neuer Schritt 1 - Es ist einfach und weniger aufdringlich, der application.properties die folgenden Eigenschaften hinzuzufügen:

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

Viel einfacher als das Ändern der vorhandenen DispatcherServlet-Instanz (wie unten)! - JO '

Wenn Sie mit einer vollständigen RESTful-Anwendung arbeiten, ist es sehr wichtig, die automatische Zuordnung statischer Ressourcen zu deaktivieren. Wenn Sie die Standardkonfiguration von Spring Boot für die Verarbeitung statischer Ressourcen verwenden, verarbeitet der Ressourcenhandler die Anforderung (sie wird zuletzt bestellt und / zugeordnet) ** Dies bedeutet, dass alle Anforderungen erfasst werden, die von keinem anderen Handler in der Anwendung verarbeitet wurden. Das Dispatcher-Servlet hat also keine Möglichkeit, eine Ausnahme auszulösen.


Neue Antwort (04.12.2015)

Verwenden von Spring Boot 1.2.7.RELEASE

Neuer Schritt 1 - Ich habe eine viel weniger aufdringliche Möglichkeit gefunden, das Flag "throExceptionIfNoHandlerFound" zu setzen. Ersetzen Sie den DispatcherServlet-Ersetzungscode unten (Schritt 1) ​​durch diesen in Ihrer Anwendungsinitialisierungsklasse:

@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 diesem Fall setzen wir das Flag für das vorhandene DispatcherServlet, wodurch die automatische Konfiguration durch das Spring Boot-Framework beibehalten wird.

Eine weitere Sache, die ich gefunden habe - die Annotation @EnableWebMvc ist für Spring Boot tödlich. Ja, diese Annotation ermöglicht es beispielsweise, alle Controller-Ausnahmen wie unten beschrieben abzufangen, aber sie beendet auch eine Menge der hilfreichen Autokonfiguration, die Spring Boot normalerweise bereitstellen würde. Verwenden Sie diese Anmerkung mit äußerster Vorsicht, wenn Sie Spring Boot verwenden.


Ursprüngliche Antwort:

Nach viel mehr Recherche und Nachverfolgung der hier veröffentlichten Lösungen (danke für die Hilfe!) Und nicht geringer Laufzeitverfolgung im Spring-Code habe ich endlich eine Konfiguration gefunden, die alle Ausnahmen behandelt (keine Fehler, aber weiterlesen). einschließlich 404s.

Schritt 1 - Weisen Sie SpringBoot an, MVC nicht mehr für Situationen zu verwenden, in denen der Handler nicht gefunden wurde. Wir möchten, dass Spring eine Ausnahme auslöst, anstatt eine auf "/ error" umgeleitete Ansicht an den Client zurückzugeben. Dazu benötigen Sie einen Eintrag in einer Ihrer Konfigurationsklassen:

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

Der Nachteil dabei ist, dass es das Standard-Dispatcher-Servlet ersetzt. Dies war noch kein Problem für uns, da keine Nebenwirkungen oder Ausführungsprobleme auftraten. Wenn Sie aus anderen Gründen etwas anderes mit dem Dispatcher-Servlet tun, ist dies der richtige Ort, um dies zu tun.

Schritt 2 - Nachdem Spring Boot nun eine Ausnahme auslöst, wenn kein Handler gefunden wird, kann diese Ausnahme mit allen anderen in einem einheitlichen Ausnahmebehandler behandelt werden:

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

Denken Sie daran, dass ich denke, dass die Annotation "@EnableWebMvc" hier von Bedeutung ist. Es scheint, dass nichts davon ohne es funktioniert. Und das war's - Ihre Spring-Boot-App erkennt jetzt alle Ausnahmen, einschließlich 404s, in der oben genannten Handler-Klasse, und Sie können sie nach Belieben verwenden.

Ein letzter Punkt - es scheint keine Möglichkeit zu geben, dies dazu zu bringen, geworfene Fehler zu fangen. Ich habe eine verrückte Idee, Aspekte zu verwenden, um Fehler zu erkennen und sie in Ausnahmen umzuwandeln, mit denen der obige Code dann umgehen kann, aber ich hatte noch keine Zeit, dies tatsächlich zu implementieren. Hoffe das hilft jemandem.

Alle Kommentare / Korrekturen / Verbesserungen werden geschätzt.

ogradyjd
quelle
Anstatt eine neue Dispatcher-Servlet-Bean zu erstellen, können Sie das Flag in einem Postprozessor umdrehen: YourClass implementiert BeanPostProcessor {... `public Object postProcessBeforeInitialization (Object Bean, String beanName) löst BeansException aus {if (Bean-Instanz von DispatcherServlet) {// andernfalls wir Holen Sie sich eine 404, bevor unser Exception-Handler die Bean ((DispatcherServlet)) aktiviert. .setThrowExceptionIfNoHandlerFound (true); } return bean; } public Object postProcessAfterInitialization (Object Bean, String BeanName) löst BeansException aus {return Bean; }
wwadge
1
Ich habe dieses Problem, aber das Anpassen des DispatcherServlet funktioniert bei mir nicht. Ist zusätzliche Magie erforderlich, damit Boot diese zusätzliche Bean und Konfiguration verwendet?
Ian Gilham
3
@ IanGilham Ich bekomme auch nicht, dass dies mit Spring Boot 1.2.7 funktioniert. Ich bekomme sogar keine @ExceptionHandlerMethode, die aufgerufen wird, wenn sie in die @ControllerAdviceKlasse eingefügt wird, obwohl sie ordnungsgemäß funktionieren, wenn sie in die @RestControllerKlasse eingefügt werden. @EnableWebMvcist in der @ControllerAdviceund der @Configuration(ich habe jede Kombination getestet) Klasse. Irgendeine Idee oder ein funktionierendes Beispiel? // @Andy Wilkinson
FrVaBe
1
Wer diese Frage und Antwort liest, sollte sich die entsprechende SpringBoot-Ausgabe auf github ansehen .
FrVaBe
1
Ich bin mir nicht sicher, ob @agpt. Ich habe ein internes Projekt, das ich auf 1.3.0 verschieben kann, um zu sehen, wie sich dies auf mein Setup auswirkt, und um Sie wissen zu lassen, was ich finde.
ogradyjd
41

Mit Spring Boot 1.4+ wurden neue coole Klassen für eine einfachere Ausnahmebehandlung hinzugefügt, die beim Entfernen des Boilerplate-Codes helfen.

Für @RestControllerAdvicedie Ausnahmebehandlung wird ein neues bereitgestellt, es ist eine Kombination aus @ControllerAdviceund @ResponseBody. Sie können die @ResponseBodyon- @ExceptionHandlerMethode entfernen , wenn Sie diese neue Anmerkung verwenden.

dh

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

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

Für die Behandlung von 404-Fehlern war das Hinzufügen von @EnableWebMvcAnmerkungen und den folgenden Elementen zu application.properties ausreichend:
spring.mvc.throw-exception-if-no-handler-found=true

Sie können die Quellen hier finden und damit spielen:
https://github.com/magiccrafter/spring-boot-exception-handling

Zauberkünstler
quelle
7
Das ist wirklich hilfreich, danke. Aber ich habe nicht verstanden, warum wir "@EnableWebMvc" mit "spring.mvc.throw-exception-if-no-handler-found = true" benötigen. Meine Erwartung war, alle Ausnahmen @RestControllerAdviceohne zusätzliche Konfiguration zu behandeln. Was fehlt mir hier?
Fiskra
28

Ich denke, ResponseEntityExceptionHandlererfüllt Ihre Anforderungen. Ein Beispielcode für 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) {
    // ...
  }
}

Sie können diesen Beitrag überprüfen

Efe Kahraman
quelle
6
Ich habe diesen Code schon einmal gesehen und nach der Implementierung hat die Klasse Ausnahmen abgefangen, die in Controller Requestmapping-Methoden ausgelöst wurden. Dies fängt immer noch keine 404-Fehler ab, die in der ResourceHttpRequestHandler.handleRequest-Methode oder, wenn die Annotation @EnableWebMvc verwendet wird, in DispatcherServlet.noHandlerFound behandelt werden. Wir möchten jeden Fehler behandeln, einschließlich 404s, aber die neueste Version von Spring Boot scheint unglaublich stumpf zu sein, wie das geht.
ogradyjd
Ich habe die gleiche Methode geschrieben, um HttpRequestMethodNotSupportedExceptiondasselbe JAR in mehreren Mikrodiensten zu verarbeiten und einzufügen. Aus geschäftlichen Gründen müssen wir in der Antwort auf den Namen des Mikrodienstalias antworten. Gibt es eine Möglichkeit, den zugrunde liegenden Namen des Mikrodienstes / Controllers zu erhalten? Ich weiß HandlerMethod, dass der Name der Java-Methode angegeben wird, von dem die Ausnahme stammt. Hier hat jedoch keine der Methoden die Anforderung erhalten und wird daher HandlerMethodnicht initialisiert. Gibt es also eine Lösung, um dies zu lösen?
Paramesh Korrakuti
Controller-Beratung ist ein guter Ansatz, aber denken Sie immer daran, dass Ausnahmen nicht Teil des Ablaufs sind, den sie in Ausnahmefällen auftreten müssen!
JorgeTovar
17

Obwohl dies eine ältere Frage ist, möchte ich meine Gedanken dazu teilen. Ich hoffe, dass es einigen von Ihnen hilfreich sein wird.

Ich erstelle derzeit eine REST-API, die Spring Boot 1.5.2.RELEASE mit Spring Framework 4.3.7.RELEASE verwendet. Ich verwende den Java Config-Ansatz (im Gegensatz zur XML-Konfiguration). Außerdem verwendet mein Projekt einen globalen Ausnahmebehandlungsmechanismus unter Verwendung der @RestControllerAdviceAnmerkung (siehe weiter unten).

Für mein Projekt gelten dieselben Anforderungen wie für Ihr Projekt: Ich möchte, dass meine REST-API eine HTTP 404 Not Foundmit einer zugehörigen JSON-Nutzlast in der HTTP-Antwort an den API-Client zurückgibt, wenn versucht wird, eine Anforderung an eine nicht vorhandene URL zu senden. In meinem Fall sieht die JSON-Nutzlast folgendermaßen aus (was sich übrigens deutlich von der Spring Boot-Standardeinstellung unterscheidet):

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

Ich habe es endlich geschafft. Hier sind die Hauptaufgaben, die Sie kurz erledigen müssen:

  • NoHandlerFoundExceptionStellen Sie sicher, dass das ausgelöst wird, wenn API-Clients URLs aufrufen, für die keine Handlermethode vorhanden ist (siehe Schritt 1 unten).
  • Erstellen Sie eine benutzerdefinierte Fehlerklasse (in meinem Fall ApiError), die alle Daten enthält, die an den API-Client zurückgegeben werden sollen (siehe Schritt 2).
  • Erstellen Sie einen Ausnahmebehandler, der auf den NoHandlerFoundException API-Client reagiert und eine entsprechende Fehlermeldung an den API-Client zurückgibt (siehe Schritt 3).
  • Schreiben Sie einen Test dafür und stellen Sie sicher, dass er funktioniert (siehe Schritt 4).

Ok, jetzt zu den Details:

Schritt 1: Konfigurieren Sie application.properties

Ich musste der Projektdatei die folgenden zwei Konfigurationseinstellungen hinzufügen application.properties:

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

Dies stellt sicher, dass das NoHandlerFoundExceptionin Fällen ausgelöst wird, in denen ein Client versucht, auf eine URL zuzugreifen, für die keine Controller-Methode vorhanden ist, die die Anforderung verarbeiten könnte.

Schritt 2: Erstellen Sie eine Klasse für API-Fehler

Ich habe eine Klasse ähnlich der in diesem Artikel auf Eugen Paraschivs Blog vorgeschlagenen gemacht. Diese Klasse repräsentiert einen API-Fehler. Diese Informationen werden im Fehlerfall an den Client im HTTP-Antworttext gesendet.

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

Schritt 3: Erstellen / Konfigurieren eines globalen Ausnahmehandlers

Ich verwende die folgende Klasse, um Ausnahmen zu behandeln (der Einfachheit halber habe ich Importanweisungen, Protokollierungscode und einige andere, nicht relevante Codeteile entfernt):

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

Schritt 4: Schreiben Sie einen Test

Ich möchte sicherstellen, dass die API auch im Fehlerfall immer die richtigen Fehlermeldungen an den aufrufenden Client zurückgibt. Also habe ich einen Test wie diesen geschrieben:

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

Die @ActiveProfiles("dev")Anmerkung kann weggelassen werden. Ich benutze es nur, wenn ich mit verschiedenen Profilen arbeite. Das RegexMatcherist ein Brauch hamcrest Matcher ich besser Felder Zeitstempel Griff verwenden. Hier ist der Code (ich habe ihn hier gefunden ):

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

Einige weitere Anmerkungen von meiner Seite:

  • In vielen anderen Posts auf StackOverflow wurde vorgeschlagen, die @EnableWebMvcAnmerkung festzulegen. Dies war in meinem Fall nicht notwendig.
  • Dieser Ansatz funktioniert gut mit MockMvc (siehe Test oben).
André Gasser
quelle
Dies löste das Problem für mich. Nur um hinzuzufügen, mir fehlte die Annotation @ RestControllerAdvice, also habe ich diese zusammen mit der Annotation @ ControllerAdvice hinzugefügt, damit sie alle handhabt, und das hat den Trick getan.
PGMacDesign
13

Was ist mit diesem Code? Ich verwende eine Fallback-Anforderungszuordnung, um 404-Fehler abzufangen.

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

Standardmäßig gibt Spring Boot json Fehlerdetails aus.

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

Es funktioniert auch für alle Arten von Anforderungszuordnungsfehlern. Überprüfen Sie diesen Artikel http://www.jayway.com/2014/10/19/spring-boot-error-responses/

Wenn Sie erstellen möchten, protokollieren Sie es bei NoSQL. Sie können @ControllerAdvice dort erstellen, wo Sie es protokollieren und dann die Ausnahme erneut auslösen würden. Es gibt ein Beispiel in der Dokumentation https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

PaintedRed
quelle
Das Standard-DispatcherServlet ist fest codiert, um die Umleitung mit MVC durchzuführen, anstatt eine Ausnahme auszulösen, wenn eine Anforderung für eine nicht vorhandene Zuordnung empfangen wird - es sei denn, Sie setzen das Flag wie im obigen Beitrag beschrieben.
ogradyjd
Der Grund, warum wir die ResponseEntityExceptionHandler-Klasse implementiert haben, besteht darin, dass wir das Format der Ausgabe- und Protokollfehlerstapel-Traces an eine NoSQL-Lösung steuern und dann eine clientseitige Fehlermeldung senden können.
ogradyjd
6

@RestControllerAdvice ist eine neue Funktion von Spring Framework 4.3 zur Behandlung von Ausnahmen mit RestfulApi durch eine übergreifende Lösung:

 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
quelle
3

Für REST-Controller würde ich die Verwendung empfehlen Zalando Problem Spring Web.

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

Wenn Spring Boot eine automatische Konfiguration einbetten möchte, bietet diese Bibliothek mehr für die Ausnahmebehandlung. Sie müssen nur die Abhängigkeit hinzufügen:

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

Definieren Sie dann ein oder mehrere Beratungsmerkmale für Ihre Ausnahmen (oder verwenden Sie die standardmäßig bereitgestellten).

public interface NotAcceptableAdviceTrait extends AdviceTrait {

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

}

Anschließend können Sie den Controller-Hinweis für die Ausnahmebehandlung wie folgt definieren:

@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {

}
Jean Valjean
quelle
2

Für Personen, die gemäß dem http-Statuscode antworten möchten, können Sie folgende Methoden verwenden ErrorController:

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

Das ResponseBeanhier ist mein benutzerdefiniertes Pojo für die Antwort.

Lym Zoy
quelle
0

Lösung mit dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);und @EnableWebMvc @ControllerAdvice arbeitete für mich mit Spring Boot 1.3.1, während nicht auf 1.2.7 arbeitete

Dennis R.
quelle