Obsługa wyjątków usługi Spring Boot REST

172

Próbuję skonfigurować serwer usług REST na dużą skalę. Używamy Spring Boot 1.2.1 Spring 4.1.5 i Java 8. Nasze kontrolery implementują @RestController i standardowe adnotacje @RequestMapping.

Mój problem polega na tym, że Spring Boot ustawia domyślne przekierowanie dla wyjątków kontrolera do /error. Z dokumentów:

Spring Boot domyślnie udostępnia mapowanie błędów /, które obsługuje wszystkie błędy w rozsądny sposób i jest rejestrowane jako „globalna” strona błędu w kontenerze serwletu.

Od lat pisząc aplikacje REST w Node.js, nie jest to dla mnie rozsądne. Każdy wyjątek generowany przez punkt końcowy usługi powinien zostać zwrócony w odpowiedzi. Nie rozumiem, dlaczego wysyłasz przekierowanie do konsumenta, który najprawdopodobniej jest konsumentem Angular lub JQuery SPA, który szuka tylko odpowiedzi i nie może lub nie może podjąć żadnych działań dotyczących przekierowania.

To, co chcę zrobić, to skonfigurować globalną procedurę obsługi błędów, która może przyjąć każdy wyjątek - celowo wyrzuconą z metody mapowania żądań lub automatycznie wygenerowaną przez Spring (404, jeśli żadna metoda obsługi nie zostanie znaleziona dla podpisu ścieżki żądania) i zwróci standardowa sformatowana odpowiedź na błąd (400, 500, 503, 404) do klienta bez żadnych przekierowań MVC. W szczególności weźmiemy błąd, zapiszemy go do NoSQL z UUID, a następnie zwrócimy klientowi właściwy kod błędu HTTP z UUID wpisu dziennika w treści JSON.

Dokumentacja nie wyjaśnia, jak to zrobić. Wydaje mi się, że musisz albo stworzyć własną implementację ErrorController, albo w jakiś sposób użyć ControllerAdvice , ale wszystkie przykłady, które widziałem, nadal obejmują przekazywanie odpowiedzi na jakieś mapowanie błędów, co nie pomaga. Inne przykłady sugerują, że musisz wymienić wszystkie typy wyjątków, które chcesz obsłużyć, zamiast po prostu wymieniać „Throwable” i pobierać wszystko.

Czy ktoś może mi powiedzieć, co przegapiłem, lub wskazać właściwy kierunek, jak to zrobić, bez sugerowania łańcucha, z którym Node.js byłby łatwiejszy do rozwiązania?

ogradyjd
źródło
6
Do klienta nigdy nie jest wysyłane przekierowanie. Przekierowanie jest obsługiwane wewnętrznie przez kontener serwletów (np. Tomcat).
OrangeDog
1
Usunięcie adnotacji @ResponseStatus z moich programów obsługi wyjątków było tym, czego potrzebowałem; zobacz stackoverflow.com/questions/35563968/…
pmorken

Odpowiedzi:

131

Nowa odpowiedź (2016-04-20)

Używanie Spring Boot 1.3.1.RELEASE

Nowy krok 1 - dodanie następujących właściwości do application.properties jest łatwe i mniej uciążliwe:

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

O wiele łatwiejsze niż modyfikowanie istniejącej instancji DispatcherServlet (jak poniżej)! - JO '

Jeśli pracujesz z pełną aplikacją RESTful, bardzo ważne jest, aby wyłączyć automatyczne mapowanie zasobów statycznych, ponieważ jeśli używasz domyślnej konfiguracji Spring Boot do obsługi zasobów statycznych, to program obsługi zasobów będzie obsługiwał żądanie (jest zamówione jako ostatnie i zmapowane do / ** co oznacza, że ​​przejmuje wszystkie żądania, które nie zostały obsłużone przez żadną inną procedurę obsługi w aplikacji), więc aplet dyspozytora nie ma szans na rzucenie wyjątku.


Nowa odpowiedź (04.12.2015)

Używanie Spring Boot 1.2.7.RELEASE

Nowy krok 1 - znalazłem znacznie mniej inwazyjny sposób ustawiania flagi „throExceptionIfNoHandlerFound”. Zastąp poniższy kod zastępczy DispatcherServlet (krok 1) tym w klasie inicjalizacji aplikacji:

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

W tym przypadku ustawiamy flagę na istniejącym DispatcherServlet, który zachowuje automatyczną konfigurację przez framework Spring Boot.

Jeszcze jedna rzecz, którą znalazłem - adnotacja @EnableWebMvc jest zabójcza dla Spring Boot. Tak, ta adnotacja umożliwia takie rzeczy, jak wychwycenie wszystkich wyjątków kontrolera, jak opisano poniżej, ale także zabija DUŻO pomocnej automatycznej konfiguracji, którą normalnie zapewnia Spring Boot. Używaj tej adnotacji z najwyższą ostrożnością podczas korzystania z Spring Boot.


Oryginalna odpowiedź:

Po wielu dalszych badaniach i śledzeniu rozwiązań zamieszczonych tutaj (dzięki za pomoc!) I niemałej ilości śledzenia środowiska wykonawczego w kodzie Spring, w końcu znalazłem konfigurację, która będzie obsługiwać wszystkie wyjątki (nie błędy, ale czytaj dalej) w tym 404s.

Krok 1 - powiedz SpringBoot, aby przestał używać MVC w sytuacjach „nie znaleziono obsługi”. Chcemy, aby Spring zgłosił wyjątek zamiast zwracać klientowi przekierowanie widoku do „/ error”. Aby to zrobić, musisz mieć wpis w jednej z klas konfiguracji:

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

Wadą tego jest to, że zastępuje domyślny serwlet dyspozytora. Nie był to jeszcze dla nas problem, bez żadnych skutków ubocznych ani problemów z wykonywaniem. Jeśli zamierzasz zrobić cokolwiek innego z serwletem dyspozytorskim z innych powodów, to jest to miejsce, w którym możesz to zrobić.

Krok 2 - Teraz, gdy rozruch sprężynowy wyrzuci wyjątek, gdy nie zostanie znaleziony żaden program obsługi, ten wyjątek może być obsłużony z każdym innym w ujednoliconej obsłudze wyjątków:

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

Pamiętaj, że uważam, że adnotacja „@EnableWebMvc” jest tutaj znacząca. Wydaje się, że bez tego nic z tego nie działa. I to wszystko - Twoja aplikacja rozruchowa Spring będzie teraz przechwytywać wszystkie wyjątki, w tym 404, w powyższej klasie obsługi i możesz z nimi robić, co chcesz.

Ostatnia kwestia - wydaje się, że nie ma sposobu, aby to złapać wyrzucone błędy. Mam szalony pomysł wykorzystania aspektów do wychwytywania błędów i przekształcania ich w wyjątki, z którymi może sobie poradzić powyższy kod, ale nie miałem jeszcze czasu, aby faktycznie spróbować to zaimplementować. Mam nadzieję, że to komuś pomoże.

Wszelkie komentarze / poprawki / ulepszenia będą mile widziane.

ogradyjd
źródło
zamiast tworzyć nowy komponent bean serwletu dispatchera możesz odwrócić flagę w postprocesorze: YourClass implementuje BeanPostProcessor {... `public Object postProcessBeforeInitialization (Object bean, String beanName) rzuca BeansException {if (bean instanceof DispatcherServlet) {// w przeciwnym razie pobierz 404 zanim nasza obsługa wyjątków uruchomi bean ((DispatcherServlet) bean) .setThrowExceptionIfNoHandlerFound (true); } return bean; } public Object postProcessAfterInitialization (obiekt bean, String beanName) rzuca BeansException {return bean; }
wwadge
1
Mam ten problem, ale dostosowywanie DispatcherServlet nie działa dla mnie. Czy do uruchomienia tej dodatkowej fasoli i konfiguracji wymagana jest jakaś dodatkowa magia?
IanGilham
3
@IanGilham Ja też nie dostaję tego do pracy z Spring Boot 1.2.7. Nawet nie otrzymuję żadnej @ExceptionHandlermetody wywoływanej podczas umieszczania jej w @ControllerAdviceklasie, chociaż działają poprawnie, jeśli zostaną umieszczone w @RestControllerklasie. @EnableWebMvcznajduje się w klasie @ControllerAdvicei @Configuration(testowałem każdą kombinację). Każdy pomysł lub działający przykład? // @Andy Wilkinson
FrVaBe
1
Ktokolwiek czyta to pytanie i odpowiedź, powinien rzucić okiem na odpowiedni numer SpringBoot na github .
FrVaBe
1
Nie wiem, @agpt. Mam wewnętrzny projekt, który mogę przenieść do wersji 1.3.0 i zobaczyć, jaki wpływ ma na moją konfigurację, i powiadomić Cię, co znalazłem.
ogradyjd
41

Wraz z Spring Boot 1.4+ dodano nowe fajne klasy dla łatwiejszej obsługi wyjątków, które pomagają w usunięciu standardowego kodu.

Nowy @RestControllerAdviceUdostępniono obsługę wyjątków, jest to kombinacja @ControllerAdvicei @ResponseBody. Można usunąć @ResponseBodyna @ExceptionHandlermetodzie kiedy korzystać z tej nowej adnotacji.

to znaczy

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

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

Do obsługi błędów 404 @EnableWebMvcwystarczyło dodanie adnotacji i następujących do application.properties:
spring.mvc.throw-exception-if-no-handler-found=true

Możesz znaleźć i bawić się źródłami tutaj:
https://github.com/magiccrafter/spring-boot-exception-handling

magiccrafter
źródło
7
To bardzo pomocne, dziękuję. Ale nie rozumiem, dlaczego musimy `@EnableWebMvc` z `spring.mvc.throw -ception-if-no-handler-found = true`. Moim oczekiwaniem było obsłużenie wszystkich wyjątków @RestControllerAdvicebez dodatkowej konfiguracji. Czego tu brakuje?
fiskra
28

Myślę, że ResponseEntityExceptionHandlerspełnia Twoje wymagania. Przykładowy fragment kodu dla 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) {
    // ...
  }
}

Możesz sprawdzić ten post

Efe Kahraman
źródło
6
Widziałem ten kod już wcześniej i po jego wdrożeniu klasa przechwyciła wyjątki zgłoszone w metodach mapowania żądań kontrolera. To nadal nie wyłapuje błędów 404, które są obsługiwane w metodzie ResourceHttpRequestHandler.handleRequest lub, jeśli jest używana adnotacja @EnableWebMvc, w DispatcherServlet.noHandlerFound. Chcemy poradzić sobie z każdym błędem, w tym 404, ale najnowsza wersja Spring Boot wydaje się być niesamowicie tępa, jeśli chodzi o to, jak to zrobić.
ogradyjd
Napisałem w ten sam sposób, aby obsługiwać HttpRequestMethodNotSupportedExceptioni podłączać ten sam plik jar do wielu mikroserwisów, w niektórych celach biznesowych musimy odpowiedzieć na nazwę aliasu mikroserwisu w odpowiedzi. czy jest jakiś sposób, abyśmy mogli uzyskać podstawową nazwę mikrousługi / nazwę kontrolera? Wiem, że podam HandlerMethodnazwę metody java, z której pochodzi wyjątek. Ale tutaj żadna z metod nie otrzymała żądania, dlatego HandlerMethodnie zostanie zainicjowana. Czy jest więc jakieś rozwiązanie tego problemu?
Paramesh Korrakuti
Porady dotyczące kontrolerów to dobre podejście, ale zawsze pamiętaj, że wyjątki nie są częścią przepływu, które muszą wystąpić w wyjątkowych przypadkach!
JorgeTovar
17

Chociaż jest to starsze pytanie, chciałbym podzielić się swoimi przemyśleniami na ten temat. Mam nadzieję, że niektórym z Was będzie to pomocne.

Obecnie buduję REST API, które wykorzystuje Spring Boot 1.5.2.RELEASE z Spring Framework 4.3.7.RELEASE. Używam podejścia Java Config (w przeciwieństwie do konfiguracji XML). Ponadto mój projekt wykorzystuje globalny mechanizm obsługi wyjątków przy użyciu @RestControllerAdviceadnotacji (patrz poniżej).

Mój projekt ma takie same wymagania jak Twój: chcę, aby mój REST API zwracał a HTTP 404 Not Foundwraz z towarzyszącym ładunkiem JSON w odpowiedzi HTTP do klienta API, gdy próbuje wysłać żądanie na adres URL, który nie istnieje. W moim przypadku ładunek JSON wygląda następująco (przy okazji wyraźnie różni się od domyślnego Spring Boot):

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

W końcu udało mi się. Oto w skrócie główne zadania, które musisz wykonać:

  • Upewnij się, że NoHandlerFoundExceptionjest generowany, jeśli klienci API wywołują adresy URL, dla których nie istnieje metoda obsługi (zobacz krok 1 poniżej).
  • Utwórz niestandardową klasę błędów (w moim przypadku ApiError), która zawiera wszystkie dane, które powinny zostać zwrócone do klienta API (patrz krok 2).
  • Utwórz procedurę obsługi wyjątków, która reaguje na NoHandlerFoundException i zwraca odpowiedni komunikat o błędzie do klienta API (patrz krok 3).
  • Napisz dla niego test i upewnij się, że działa (patrz krok 4).

Ok, teraz do szczegółów:

Krok 1: Skonfiguruj application.properties

Musiałem dodać następujące dwa ustawienia konfiguracyjne do application.propertiespliku projektu :

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

Zapewnia to, że NoHandlerFoundExceptionjest generowany w przypadkach, gdy klient próbuje uzyskać dostęp do adresu URL, dla którego nie istnieje metoda kontrolera, która byłaby w stanie obsłużyć żądanie.

Krok 2: Utwórz klasę dla błędów interfejsu API

Zrobiłem zajęcia podobne do sugerowanych w tym artykule na blogu Eugena Paraschiva. Ta klasa reprezentuje błąd interfejsu API. Ta informacja jest wysyłana do klienta w treści odpowiedzi HTTP w przypadku błędu.

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

Krok 3: Utwórz / skonfiguruj globalną obsługę wyjątków

Do obsługi wyjątków używam następującej klasy (dla uproszczenia usunąłem instrukcje importu, kod logowania i kilka innych, nieistotnych fragmentów kodu):

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

Krok 4: Napisz test

Chcę się upewnić, że API zawsze zwraca poprawne komunikaty o błędach do klienta wywołującego, nawet w przypadku niepowodzenia. Dlatego napisałem taki test:

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

@ActiveProfiles("dev")Adnotacja może pozostać z dala. Używam go tylko wtedy, gdy pracuję z różnymi profilami. RegexMatcherJest zwyczaj Hamcrest dopasowujący używać do lepszych pól datownika uchwytem. Oto kod (znalazłem go tutaj ):

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

Kilka dalszych uwag z mojej strony:

  • W wielu innych postach w StackOverflow ludzie sugerowali ustawienie @EnableWebMvcadnotacji. W moim przypadku nie było to konieczne.
  • To podejście działa dobrze z MockMvc (patrz test powyżej).
André Gasser
źródło
To rozwiązało problem. Żeby dodać, brakowało mi adnotacji @ RestControllerAdvice, więc dodałem ją wraz z adnotacją @ ControllerAdvice, aby obsłużyć wszystko i to załatwiło sprawę.
PGMacDesign
13

A co z tym kodem? Używam mapowania żądań rezerwowych, aby wychwycić błędy 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
źródło
6

Domyślnie Spring Boot wyświetla json ze szczegółami błędów.

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

Działa również w przypadku wszelkiego rodzaju błędów mapowania żądań. Sprawdź ten artykuł http://www.jayway.com/2014/10/19/spring-boot-error-responses/

Jeśli chcesz utworzyć, zaloguj się do NoSQL. Możesz utworzyć @ControllerAdvice, w którym chcesz to zarejestrować, a następnie ponownie zgłosić wyjątek. W dokumentacji jest przykład https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

PaintedRed
źródło
Domyślny DispatcherServlet jest zakodowany na sztywno, aby wykonać przekierowanie z MVC, zamiast zgłaszać wyjątek, gdy zostanie odebrane żądanie nieistniejącego mapowania - chyba że ustawisz flagę tak, jak to zrobiłem w poście powyżej.
ogradyjd
Ponadto, powodem, dla którego zaimplementowaliśmy klasę ResponseEntityExceptionHandler, jest to, że mogliśmy kontrolować format danych wyjściowych i rejestrować ślady stosu błędów do rozwiązania NoSQL, a następnie wysyłać bezpieczny dla klienta komunikat o błędzie.
ogradyjd
6

@RestControllerAdvice to nowa funkcja Spring Framework 4.3 do obsługi wyjątków z RestfulApi poprzez rozwiązanie problemu:

 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
źródło
3

W przypadku kontrolerów REST polecałbym użyć Zalando Problem Spring Web.

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

Jeśli Spring Boot ma na celu osadzenie automatycznej konfiguracji, ta biblioteka robi więcej w zakresie obsługi wyjątków. Wystarczy dodać zależność:

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

Następnie zdefiniuj jedną lub więcej cech porad dla wyjątków (lub użyj tych, które są domyślnie)

public interface NotAcceptableAdviceTrait extends AdviceTrait {

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

}

Następnie możesz zdefiniować porady kontrolera dotyczące obsługi wyjątków jako:

@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {

}
Jean Valjean
źródło
2

Dla osób, które chcą odpowiadać zgodnie z kodem statusu http, możesz skorzystać ze ErrorControllersposobu:

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

ResponseBeanTutaj jest mój zwyczaj pojo za odpowiedź.

Lym Zoy
źródło
0

Rozwiązanie z dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);i @EnableWebMvc @ControllerAdvice pracował dla mnie z wiosennym Boot 1.3.1, natomiast nie działa na 1.2.7

Dennis R.
źródło