Manejo de excepciones del servicio Spring Boot REST

173

Estoy tratando de configurar un servidor de servicios REST a gran escala. Estamos utilizando Spring Boot 1.2.1 Spring 4.1.5 y Java 8. Nuestros controladores están implementando @RestController y las anotaciones estándar @RequestMapping.

Mi problema es que Spring Boot configura una redirección predeterminada para las excepciones del controlador /error. De los documentos:

Spring Boot proporciona una asignación / error por defecto que maneja todos los errores de una manera sensata, y está registrada como una página de error 'global' en el contenedor de servlets.

Viniendo de años escribiendo aplicaciones REST con Node.js, esto es, para mí, todo menos sensato. Cualquier excepción que genere un punto final de servicio debería volver en la respuesta. No puedo entender por qué enviaría una redirección a lo que probablemente sea un consumidor de Angular o JQuery SPA que solo está buscando una respuesta y no puede o no tomará ninguna acción en una redirección.

Lo que quiero hacer es configurar un controlador de errores global que pueda tomar cualquier excepción, ya sea arrojado intencionalmente desde un método de mapeo de solicitud o generado automáticamente por Spring (404 si no se encuentra ningún método de controlador para la firma de ruta de solicitud), y devolver un respuesta de error con formato estándar (400, 500, 503, 404) al cliente sin ningún redireccionamiento MVC. Específicamente, vamos a tomar el error, registrarlo en NoSQL con un UUID, luego devolver al cliente el código de error HTTP correcto con el UUID de la entrada de registro en el cuerpo JSON.

Los documentos han sido vagos sobre cómo hacer esto. Me parece que tiene que crear su propia implementación de ErrorController o usar ControllerAdvice de alguna manera, pero todos los ejemplos que he visto todavía incluyen reenviar la respuesta a algún tipo de mapeo de errores, lo que no ayuda. Otros ejemplos sugieren que tendría que enumerar todos los tipos de Excepción que desea manejar en lugar de simplemente enumerar "Throwable" y obtener todo.

¿Alguien puede decirme lo que me perdí o señalarme en la dirección correcta cómo hacerlo sin sugerir la cadena con la que Node.js sería más fácil de tratar?

ogradyjd
fuente
66
Al cliente nunca se le envía una redirección. La redirección es manejada internamente por el contenedor de servlet (por ejemplo, Tomcat).
OrangeDog
1
Lo que necesitaba era eliminar las anotaciones @ResponseStatus en mis controladores de excepciones; ver stackoverflow.com/questions/35563968/…
pmorken

Respuestas:

131

Nueva respuesta (20/04/2016)

Usando Spring Boot 1.3.1.RELEASE

Nuevo paso 1: es fácil y menos intrusivo agregar las siguientes propiedades a la aplicación.

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

¡Mucho más fácil que modificar la instancia existente de DispatcherServlet (como se muestra a continuación)! - JO '

Si trabaja con una aplicación RESTful completa, es muy importante deshabilitar la asignación automática de recursos estáticos, ya que si está utilizando la configuración predeterminada de Spring Boot para manejar recursos estáticos, el controlador de recursos manejará la solicitud (se ordena al final y se asigna a / ** lo que significa que recoge cualquier solicitud que no haya sido manejada por ningún otro controlador en la aplicación) para que el servlet del despachador no tenga la oportunidad de lanzar una excepción.


Nueva respuesta (04/12/2015)

Usando Spring Boot 1.2.7.RELEASE

Nuevo paso 1: encontré una forma mucho menos intrusiva de configurar el indicador "throExceptionIfNoHandlerFound". Reemplace el código de reemplazo de DispatcherServlet a continuación (Paso 1) con esto en la clase de inicialización de su aplicación:

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

En este caso, estamos configurando el indicador en el DispatcherServlet existente, que conserva cualquier configuración automática por parte del marco Spring Boot.

Una cosa más que he encontrado: la anotación @EnableWebMvc es mortal para Spring Boot. Sí, esa anotación permite cosas como poder capturar todas las excepciones del controlador como se describe a continuación, pero también elimina MUCHA de la útil configuración automática que Spring Boot proporcionaría normalmente. Use esa anotación con extrema precaución cuando use Spring Boot.


Respuesta original

Después de mucha más investigación y seguimiento de las soluciones publicadas aquí (¡gracias por la ayuda!) Y una pequeña cantidad de tiempo de ejecución en el código Spring, finalmente encontré una configuración que manejará todas las Excepciones (no Errores, pero sigue leyendo) incluyendo 404s.

Paso 1: dígale a SpringBoot que deje de usar MVC para situaciones de "controlador no encontrado". Queremos que Spring arroje una excepción en lugar de devolver al cliente una vista redirigida a "/ error". Para hacer esto, debe tener una entrada en una de sus clases de configuración:

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

La desventaja de esto es que reemplaza el servlet del despachador predeterminado. Esto no ha sido un problema para nosotros todavía, sin efectos secundarios ni problemas de ejecución. Si va a hacer algo más con el servlet del despachador por otros motivos, este es el lugar para hacerlo.

Paso 2: ahora que el arranque de primavera generará una excepción cuando no se encuentre un controlador, esa excepción se puede manejar con cualquier otro en un controlador de excepciones 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);
    }
    ...
}

Tenga en cuenta que creo que la anotación "@EnableWebMvc" es importante aquí. Parece que nada de esto funciona sin él. Y eso es todo: su aplicación de arranque Spring ahora detectará todas las excepciones, incluidas las 404, en la clase de controlador anterior y puede hacerlas como desee.

Un último punto: no parece haber una manera de hacer que esto atrape los errores arrojados. Tengo una extraña idea de usar aspectos para detectar errores y convertirlos en Excepciones con las que el código anterior puede lidiar, pero aún no he tenido tiempo de intentar implementarlo. Espero que esto ayude a alguien.

Cualquier comentario / corrección / mejora será apreciado.

ogradyjd
fuente
en lugar de crear un nuevo servlet bean de despachador, puede voltear la bandera en un post procesador: YourClass implementa BeanPostProcessor {... `public Object postProcessBeforeInitialization (Object bean, String beanName) arroja BeansException {if (bean instanceof DispatcherServlet) {// de lo contrario obtener un 404 antes de que nuestro controlador de excepciones entre en ((DispatcherServlet) bean) .setThrowExceptionIfNoHandlerFound (true); } return bean; } Public Object postProcessAfterInitialization (Object bean, String beanName) arroja BeansException {return bean; }
wwadge
1
Tengo este problema, pero personalizar el DispatcherServlet no funciona para mí. ¿Hay alguna magia adicional requerida para que Boot use este bean y configuración extra?
IanGilham
3
@IanGilham Yo tampoco hago que esto funcione con Spring Boot 1.2.7. Incluso no recibo ningún @ExceptionHandlermétodo al colocarlo en la @ControllerAdviceclase, aunque funcionan correctamente si se colocan en la @RestControllerclase. @EnableWebMvcestá en la clase @ControllerAdvicey @Configuration(probé cada combinación). ¿Alguna idea o ejemplo de trabajo? // @Andy Wilkinson
FrVaBe
1
Quien lea esta pregunta y respuesta debe echar un vistazo al correspondiente tema de SpringBoot en github .
Viernes
1
No estoy seguro @agpt. Tengo un proyecto interno que puedo mover hasta 1.3.0 y ver cuál es el efecto en mi configuración y hacerle saber lo que encuentro.
ogradyjd
41

Con Spring Boot 1.4+ se agregaron nuevas clases interesantes para un manejo de excepciones más fácil que ayuda a eliminar el código repetitivo.

Se @RestControllerAdviceproporciona una nueva para el manejo de excepciones, es una combinación de @ControllerAdvicey @ResponseBody. Se puede quitar el @ResponseBodyen el @ExceptionHandlermétodo cuando se utiliza esta nueva anotación.

es decir

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

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

Para manejar errores 404, agregar @EnableWebMvcanotaciones y lo siguiente a application.properties fue suficiente:
spring.mvc.throw-exception-if-no-handler-found=true

Puedes encontrar y jugar con las fuentes aquí:
https://github.com/magiccrafter/spring-boot-exception-handling

magiccrafter
fuente
77
Eso es realmente útil, gracias. Pero no entendí por qué necesitamos `@EnableWebMvc` con `spring.mvc.throw-exception-if-no-handler-found = true`. Mi expectativa era manejar todas las excepciones @RestControllerAdvicesin configuración adicional. ¿Que me estoy perdiendo aqui?
fiskra
28

Creo que ResponseEntityExceptionHandlercumple con tus requisitos. Una pieza de código de muestra 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) {
    // ...
  }
}

Puedes consultar esta publicación

Efe Kahraman
fuente
66
He visto este código antes, y después de implementarlo, la clase captó las excepciones generadas en los métodos de mapeo de solicitud del controlador. Esto todavía no detecta errores 404, que se manejan en el método ResourceHttpRequestHandler.handleRequest o, si se usa la anotación @EnableWebMvc, en DispatcherServlet.noHandlerFound. Queremos manejar cualquier error, incluidos los 404, pero la última versión de Spring Boot parece ser increíblemente obtusa sobre cómo hacerlo.
ogradyjd
Escribí la misma manera de manejar HttpRequestMethodNotSupportedExceptiony conectar el mismo jar en múltiples microservicios, para algún propósito comercial necesitamos responder el nombre del alias del micro servicio en la respuesta. ¿Hay alguna manera de que podamos obtener el nombre del micro servicio / nombre del controlador subyacente? Sé HandlerMethodque proporcionará el nombre del método Java desde donde se origina la excepción. Pero aquí, ninguno de los métodos recibió la solicitud, por HandlerMethodlo tanto , no se inicializará. Entonces, ¿hay alguna solución para resolver esto?
Paramesh Korrakuti
El consejo del controlador es un buen enfoque, pero siempre recuerde que las excepciones no son parte del flujo, ¡deben ocurrir en casos excepcionales!
JorgeTovar
17

Aunque esta es una pregunta anterior, me gustaría compartir mis pensamientos al respecto. Espero que sea útil para algunos de ustedes.

Actualmente estoy construyendo una API REST que hace uso de Spring Boot 1.5.2.RELEASE con Spring Framework 4.3.7.RELEASE. Yo uso el enfoque de Java Config (a diferencia de la configuración XML). Además, mi proyecto usa un mecanismo global de manejo de excepciones usando la @RestControllerAdviceanotación (ver más adelante).

Mi proyecto tiene los mismos requisitos que el suyo: quiero que mi API REST devuelva un HTTP 404 Not Foundmensaje con una carga útil JSON adjunta en la respuesta HTTP al cliente API cuando intenta enviar una solicitud a una URL que no existe. En mi caso, la carga útil de JSON se ve así (que claramente difiere del valor predeterminado de Spring Boot, por cierto):

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

Finalmente lo hice funcionar. Estas son las tareas principales que debe hacer en resumen:

  • Asegúrese de que NoHandlerFoundExceptionse lance si los clientes API llaman a URLS para las cuales no existe ningún método de controlador (consulte el Paso 1 a continuación).
  • Cree una clase de error personalizada (en mi caso ApiError) que contenga todos los datos que deben devolverse al cliente API (consulte el paso 2).
  • Cree un manejador de excepciones que reaccione NoHandlerFoundException y devuelva un mensaje de error adecuado al cliente API (consulte el paso 3).
  • Escriba una prueba y asegúrese de que funciona (vea el paso 4).

Ok, ahora a los detalles:

Paso 1: Configurar application.properties

Tuve que agregar las siguientes dos configuraciones al application.propertiesarchivo del proyecto :

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

Esto garantiza que NoHandlerFoundExceptionse lance en los casos en que un cliente intente acceder a una URL para la cual no existe un método de controlador que pueda manejar la solicitud.

Paso 2: crear una clase para errores de API

Hice una clase similar a la sugerida en este artículo en el blog de Eugen Paraschiv. Esta clase representa un error de API. Esta información se envía al cliente en el cuerpo de respuesta HTTP en caso de error.

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

Paso 3: crear / configurar un controlador de excepción global

Utilizo la siguiente clase para manejar excepciones (por simplicidad, he eliminado las declaraciones de importación, el código de registro y algunos otros fragmentos de código no relevantes):

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

Paso 4: escribe una prueba

Quiero asegurarme de que la API siempre devuelve los mensajes de error correctos al cliente que realiza la llamada, incluso en caso de falla. Por lo tanto, escribí una prueba como esta:

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

La @ActiveProfiles("dev")anotación se puede dejar de lado. Lo uso solo cuando trabajo con diferentes perfiles. El RegexMatcheres un emparejador Hamcrest personalizado que uso para manejar mejor los campos de marca de tiempo. Aquí está el código (lo encontré aquí ):

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

Algunas notas adicionales de mi parte:

  • En muchas otras publicaciones en StackOverflow, las personas sugirieron configurar la @EnableWebMvcanotación. Esto no fue necesario en mi caso.
  • Este enfoque funciona bien con MockMvc (ver prueba anterior).
André Gasser
fuente
Esto resolvió mi problema. Solo para agregar, me faltaba la anotación @ RestControllerAdvice, así que agregué eso junto con la anotación @ ControllerAdvice para que pudiera manejar todo y eso funcionó.
PGMacDesign
13

¿Qué hay de este código? Utilizo una asignación de solicitud de reserva para detectar errores 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
fuente
6

Por defecto, Spring Boot proporciona json con detalles de error.

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

También funciona para todo tipo de errores de mapeo de solicitudes. Consulte este artículo http://www.jayway.com/2014/10/19/spring-boot-error-responses/

Si desea crear, inicie sesión en NoSQL. Puede crear @ControllerAdvice donde lo registraría y luego volver a lanzar la excepción. Hay un ejemplo en la documentación https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

PintadoRojo
fuente
El DispatcherServlet predeterminado está codificado para hacer la redirección con MVC en lugar de lanzar una excepción cuando se recibe una solicitud de mapeo inexistente, a menos que establezca la bandera como lo hice en la publicación anterior.
ogradyjd
Además, la razón por la que implementamos la clase ResponseEntityExceptionHandler es para poder controlar el formato de la salida y registrar los seguimientos de la pila de errores a una solución NoSQL y luego enviar un mensaje de error seguro para el cliente.
ogradyjd
6

@RestControllerAdvice es una nueva característica de Spring Framework 4.3 para manejar Exception with RestfulApi mediante una solución de preocupación 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
fuente
3

Para los controladores REST, recomendaría usar Zalando Problem Spring Web.

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

Si Spring Boot tiene como objetivo incrustar alguna configuración automática, esta biblioteca hace más para el manejo de excepciones. Solo necesita agregar la dependencia:

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

Y luego defina uno o más rasgos de asesoramiento para sus excepciones (o use los que se proporcionan por defecto)

public interface NotAcceptableAdviceTrait extends AdviceTrait {

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

}

Luego puede definir el consejo del controlador para el manejo de excepciones como:

@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {

}
JeanValjean
fuente
2

Para las personas que desean responder de acuerdo con el código de estado http, puede usar la ErrorControllerforma:

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

La ResponseBeanAquí está mi POJO personalizado para la respuesta.

Lym Zoy
fuente
0

Solución dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);y @EnableWebMvc @ControllerAdvice funcionó para mí con Spring Boot 1.3.1, mientras no funcionaba en 1.2.7

Dennis R
fuente