Manipulação de exceção do serviço Spring Boot REST

173

Estou tentando configurar um servidor de serviços REST em larga escala. Estamos usando o Spring Boot 1.2.1 Spring 4.1.5 e o Java 8. Nossos controladores estão implementando o @RestController e as anotações padrão @RequestMapping.

Meu problema é que o Spring Boot configura um redirecionamento padrão para exceções do controlador /error. Dos documentos:

O Spring Boot fornece um mapeamento / error por padrão que lida com todos os erros de maneira sensata e é registrado como uma página de erro 'global' no contêiner do servlet.

Vindo de anos escrevendo aplicativos REST com Node.js, isso é, para mim, tudo menos sensato. Qualquer exceção gerada por um terminal em serviço deve retornar na resposta. Não consigo entender por que você enviaria um redirecionamento para o que provavelmente é um consumidor do Angular ou JQuery SPA, que está apenas procurando uma resposta e não pode ou não executa nenhuma ação em um redirecionamento.

O que eu quero fazer é configurar um manipulador de erro global que possa ter qualquer exceção - lançado propositalmente a partir de um método de mapeamento de solicitação ou gerado automaticamente pelo Spring (404 se nenhum método de manipulador for encontrado para a assinatura do caminho da solicitação) e retornar um resposta de erro formatada padrão (400, 500, 503, 404) ao cliente sem nenhum redirecionamento MVC. Especificamente, pegaremos o erro, registrá-lo no NoSQL com um UUID e, em seguida, retornar ao cliente o código de erro HTTP correto com o UUID da entrada de log no corpo JSON.

Os documentos têm sido vagos sobre como fazer isso. Parece-me que você precisa criar sua própria implementação ErrorController ou usar o ControllerAdvice de alguma forma, mas todos os exemplos que vi ainda incluem encaminhar a resposta para algum tipo de mapeamento de erro, o que não ajuda. Outros exemplos sugerem que você precisa listar todos os tipos de exceção que deseja manipular, em vez de apenas listar "Throwable" e obter tudo.

Alguém pode me dizer o que eu perdi ou me indicar a direção certa sobre como fazer isso sem sugerir a cadeia com a qual o Node.js seria mais fácil de lidar?

ogradyjd
fonte
6
O cliente nunca recebe realmente um redirecionamento. O redirecionamento é tratado internamente pelo contêiner do servlet (por exemplo, Tomcat).
OrangeDog 24/02
1
Remover as anotações @ResponseStatus nos meus manipuladores de exceção era o que eu precisava; consulte stackoverflow.com/questions/35563968/…
pmorken 30/03

Respostas:

131

Nova resposta (20-04-2016)

Usando Spring Boot 1.3.1.RELEASE

Nova Etapa 1 - É fácil e menos invasivo adicionar as seguintes propriedades ao application.properties:

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

Muito mais fácil do que modificar a instância existente do DispatcherServlet (como abaixo)! - JO '

Se estiver trabalhando com um aplicativo RESTful completo, é muito importante desabilitar o mapeamento automático de recursos estáticos, pois se você estiver usando a configuração padrão do Spring Boot para manipular recursos estáticos, o manipulador de recursos manipulará a solicitação (ela é ordenada por último e mapeada para / **, o que significa que ele coleta todas as solicitações que não foram tratadas por nenhum outro manipulador no aplicativo) para que o servlet do despachante não tenha a chance de gerar uma exceção.


Nova resposta (04-12-2015)

Usando o Spring Boot 1.2.7.RELEASE

Nova etapa 1 - Encontrei uma maneira muito menos intrusiva de definir o sinalizador "throExceptionIfNoHandlerFound". Substitua o código de substituição DispatcherServlet abaixo (Etapa 1) por este na sua classe de inicialização do aplicativo:

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

Nesse caso, estamos definindo o sinalizador no DispatcherServlet existente, que preserva qualquer configuração automática pela estrutura do Spring Boot.

Mais uma coisa que eu encontrei - a anotação @EnableWebMvc é mortal para o Spring Boot. Sim, essa anotação permite capturar todas as exceções do controlador, conforme descrito abaixo, mas também mata MUITAS configurações automáticas úteis que o Spring Boot normalmente forneceria. Use essa anotação com extrema cautela ao usar o Spring Boot.


Resposta original:

Depois de muito mais pesquisa e acompanhamento das soluções postadas aqui (obrigado pela ajuda!) E pouca quantidade de rastreamento do tempo de execução no código Spring, finalmente encontrei uma configuração que tratará de todas as exceções (não erros, mas continue lendo) incluindo 404s.

Etapa 1 - diga ao SpringBoot para parar de usar o MVC para situações de "manipulador não encontrado". Queremos que o Spring lance uma exceção em vez de retornar ao cliente um redirecionamento de exibição para "/ error". Para fazer isso, você precisa ter uma entrada em uma de suas classes de configuração:

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

A desvantagem disso é que ele substitui o servlet padrão do despachante. Isso ainda não foi um problema para nós, sem efeitos colaterais ou problemas de execução aparecendo. Se você quiser fazer mais alguma coisa com o servlet do expedidor por outros motivos, este é o lugar para fazê-lo.

Etapa 2 - Agora que a inicialização por primavera lançará uma exceção quando nenhum manipulador for encontrado, essa exceção poderá ser tratada com outras pessoas em um manipulador de exceção unificado:

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

Lembre-se de que a anotação "@EnableWebMvc" é significativa aqui. Parece que nada disso funciona sem ele. E é isso: seu aplicativo de inicialização do Spring agora captura todas as exceções, incluindo 404s, na classe de manipulador acima e você pode fazer o que quiser.

Um último ponto - não parece haver uma maneira de fazer com que isso apareça erros lançados. Eu tenho uma idéia maluca de usar aspectos para capturar erros e transformá-los em exceções com as quais o código acima pode lidar, mas ainda não tive tempo para realmente tentar implementá-lo. Espero que isso ajude alguém.

Quaisquer comentários / correções / melhorias serão apreciados.

ogradyjd
fonte
em vez de criar um novo bean de servlet do despachante, você pode inverter o sinalizador em um pós-processador: YourClass implementa BeanPostProcessor {... `public Object postProcessBeforeInitialization (Bean de objeto, String beanName) lança BeansException {if (bean instanceof DispatcherServlet) {// caso contrário, we obter um 404 antes que nosso manipulador de exceção inicie o bean ((DispatcherServlet)) .setThrowExceptionIfNoHandlerFound (true); } retornar bean; } public Object postProcessAfterInitialization (Bean de objeto, String beanName) lança BeansException {return bean; }
wwadge 20/08/2015
1
Eu tenho esse problema, mas a personalização do DispatcherServlet não funciona para mim. Existe alguma mágica adicional necessária para o Boot usar esse bean e configuração extras?
IanGilham
3
@IanGilham Eu também não faço isso funcionar com o Spring Boot 1.2.7. Eu ainda não recebi nenhum @ExceptionHandlermétodo chamado ao colocá-lo na @ControllerAdviceclasse, embora eles funcionem corretamente se colocados na @RestControllerclasse. @EnableWebMvcestá na classe @ControllerAdvicee @Configuration(eu testei todas as combinações). Alguma idéia ou exemplo de trabalho? // @Andy Wilkinson
FrVaBe
1
Quem lê esta pergunta e resposta deve dar uma olhada na edição correspondente do SpringBoot no github .
FrVaBe
1
Não tenho certeza @agpt. Eu tenho um projeto interno que posso mudar para a 1.3.0 e ver qual é o efeito na minha instalação e informar o que encontro.
ogradyjd
41

Com o Spring Boot 1.4+, novas classes interessantes para facilitar o manuseio de exceções foram adicionadas, ajudando a remover o código padrão.

Um novo @RestControllerAdviceé fornecido para manipulação de exceção, é a combinação de @ControllerAdvicee @ResponseBody. Você pode remover o @ResponseBodysobre o @ExceptionHandlermétodo quando usar esta nova anotação.

ie

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

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

Para manipular erros 404, adicionar @EnableWebMvcanotação e o seguinte a application.properties foi suficiente:
spring.mvc.throw-exception-if-no-handler-found=true

Você pode encontrar e brincar com as fontes aqui:
https://github.com/magiccrafter/spring-boot-exception-handling

artesão
fonte
7
Isso é realmente útil, obrigado. Mas não entendi por que precisamos `@EnableWebMvc` com `spring.mvc.throw-exception-if-no-handler-found = true`. Minha expectativa era lidar com todas as exceções via @RestControllerAdvicesem configuração adicional. O que estou perdendo aqui?
fiskra
28

Eu acho que ResponseEntityExceptionHandleratende às suas necessidades. Um exemplo de código para 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) {
    // ...
  }
}

Você pode verificar esta postagem

Efe Kahraman
fonte
6
Eu já vi esse código antes e, após implementá-lo, a classe capturou exceções geradas nos métodos de mapeamento de pedidos do controlador. Isso ainda não captura erros 404, que estão sendo tratados no método ResourceHttpRequestHandler.handleRequest ou, se a anotação @EnableWebMvc for usada, em DispatcherServlet.noHandlerFound. Queremos lidar com qualquer erro, incluindo 404, mas a versão mais recente do Spring Boot parece incrivelmente obtusa sobre como fazer isso.
ogradyjd
Eu escrevi da mesma maneira para manipular HttpRequestMethodNotSupportedExceptione plug-in o mesmo jar em vários microsserviços, para algum objetivo comercial precisamos responder o nome alternativo do microsserviço na resposta. existe alguma maneira de obter o nome do micro-serviço / nome do controlador subjacente? Eu sei que HandlerMethodirá fornecer o nome do método java de onde a exceção é originada. Mas aqui, nenhum dos métodos recebeu a solicitação, portanto HandlerMethod, não será inicializado. Então, existe alguma solução para resolver isso?
Paramesh Korrakuti 14/09/18
O conselho do controlador é uma boa abordagem, mas lembre-se sempre de que as exceções não fazem parte do fluxo; elas devem ocorrer em casos excepcionais!
JorgeTovar 11/06/19
17

Embora essa seja uma pergunta antiga, gostaria de compartilhar meus pensamentos sobre isso. Espero que seja útil para alguns de vocês.

Atualmente, estou criando uma API REST que utiliza o Spring Boot 1.5.2.RELEASE com o Spring Framework 4.3.7.RELEASE. Eu uso a abordagem Java Config (em oposição à configuração XML). Além disso, meu projeto usa um mecanismo de manipulação de exceção global usando a @RestControllerAdviceanotação (veja mais adiante).

Meu projeto tem os mesmos requisitos que o seu: Desejo que minha API REST retorne um HTTP 404 Not Foundcom uma carga JSON correspondente na resposta HTTP ao cliente da API quando tentar enviar uma solicitação para um URL que não existe. No meu caso, a carga útil JSON se parece com esta (que difere claramente do padrão Spring Boot, btw.):

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

Eu finalmente fiz funcionar. Aqui estão as principais tarefas que você precisa executar em breve:

  • Certifique-se de que isso NoHandlerFoundExceptionseja acionado se os clientes da API chamarem URLS para os quais não existe método manipulador (consulte a Etapa 1 abaixo).
  • Crie uma classe de erro personalizada (no meu caso ApiError) que contenha todos os dados que devem ser retornados ao cliente da API (consulte a etapa 2).
  • Crie um manipulador de exceções que reaja NoHandlerFoundException e retorne uma mensagem de erro adequada ao cliente da API (consulte a etapa 3).
  • Faça um teste e verifique se funciona (consulte a etapa 4).

Ok, agora vamos aos detalhes:

Etapa 1: Configurar application.properties

Eu tive que adicionar as duas configurações a seguir no application.propertiesarquivo do projeto :

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

Isso garante que ele NoHandlerFoundExceptionseja lançado nos casos em que um cliente tente acessar uma URL para a qual não exista nenhum método de controlador capaz de lidar com a solicitação.

Etapa 2: criar uma classe para erros de API

Fiz uma aula semelhante à sugerida neste artigo no blog de Eugen Paraschiv. Esta classe representa um erro de API. Essas informações são enviadas ao cliente no corpo da resposta HTTP em caso de erro.

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

Etapa 3: Criar / configurar um manipulador de exceção global

Uso a seguinte classe para lidar com exceções (por simplicidade, removi instruções de importação, código de log e algumas outras partes não relevantes do código):

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

Etapa 4: escreva um teste

Quero ter certeza de que a API sempre retorna as mensagens de erro corretas para o cliente que está chamando, mesmo em caso de falha. Assim, eu escrevi um teste como este:

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

A @ActiveProfiles("dev")anotação pode ser deixada de fora. Eu o uso apenas enquanto trabalho com perfis diferentes. O RegexMatcheré um combinador Hamcrest personalizado que eu uso para lidar melhor com os campos de registro de data e hora. Aqui está o código (eu encontrei aqui ):

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

Algumas notas adicionais do meu lado:

  • Em muitas outras postagens no StackOverflow, as pessoas sugeriram definir a @EnableWebMvcanotação. Isso não foi necessário no meu caso.
  • Essa abordagem funciona bem com o MockMvc (veja o teste acima).
André Gasser
fonte
Isso resolveu o problema para mim. Só para adicionar, estava faltando a anotação @ RestControllerAdvice, então eu a adicionei junto com a anotação @ ControllerAdvice, para que ela pudesse lidar com tudo e isso funcionou.
PGMacDesign
13

E esse código? Eu uso um mapeamento de solicitação de fallback para capturar erros 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
fonte
6

Por padrão, o Spring Boot fornece ao json detalhes de erro.

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

Também funciona para todos os tipos de erros de mapeamento de solicitação. Verifique este artigo http://www.jayway.com/2014/10/19/spring-boot-error-responses/

Se você deseja criar, registre-o no NoSQL. Você pode criar o @ControllerAdvice onde o registraria e, em seguida, lance novamente a exceção. Há um exemplo na documentação https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

PintadoVermelho
fonte
O DispatcherServlet padrão é codificado para fazer o redirecionamento com o MVC em vez de lançar uma exceção quando uma solicitação para um mapeamento inexistente é recebida - a menos que você defina o sinalizador como fiz na postagem acima.
ogradyjd
Além disso, o motivo pelo qual implementamos a classe ResponseEntityExceptionHandler é para podermos controlar o formato dos rastreamentos da pilha de erros de saída e log em uma solução NoSQL e enviar uma mensagem de erro segura para o cliente.
ogradyjd
6

O @RestControllerAdvice é um novo recurso do Spring Framework 4.3 para lidar com a Exceção com RestfulApi por meio de uma solução de interesse transversal:

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

Para controladores REST, eu recomendaria usar Zalando Problem Spring Web.

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

Se o Spring Boot pretende incorporar alguma configuração automática, esta biblioteca faz mais pelo tratamento de exceções. Você só precisa adicionar a dependência:

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

E, em seguida, defina uma ou mais características de aconselhamento para suas exceções (ou use as fornecidas por padrão)

public interface NotAcceptableAdviceTrait extends AdviceTrait {

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

}

Em seguida, você pode definir o conselho do controlador para tratamento de exceções como:

@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {

}
Jean Valjean
fonte
2

Para pessoas que desejam responder de acordo com o código de status http, você pode usar o ErrorControllercaminho:

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

O ResponseBeanaqui é o meu pojo personalizado para resposta.

Lym Zoy
fonte
0

Solução dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);e @EnableWebMvc @ControllerAdvice funcionou para mim com o Spring Boot 1.3.1, enquanto não estava funcionando no 1.2.7

Dennis R
fonte