Manejo de excepciones en controladores Spring

imagen



En la práctica, a menudo es necesario gestionar de forma centralizada las excepciones dentro de un controlador o incluso una aplicación completa. En este artículo analizaremos las principales características que proporciona Spring Framework para solucionar este problema y, utilizando ejemplos sencillos, veremos cómo funciona todo. ¿Quién está interesado en este tema - bienvenido bajo el corte!



Originalmente, antes de Spring 3.2, las principales formas de manejar excepciones en una aplicación eran HandlerExceptionResolver y la anotación @ExceptionHandler . Los analizaremos con más detalle a continuación, pero tienen ciertos inconvenientes. A partir de la versión 3.2, se ha introducido la anotación @ControllerAdvice , que elimina las limitaciones de las soluciones anteriores. Y en Spring 5, se agregó una nueva clase ResponseStatusException que es muy útil para manejar errores básicos para las API REST .



Y ahora, lo primero es lo primero, ¡vamos!



Manejo de excepciones del controlador - @ExceptionHandler



@ExceptionHandler . , , .



:



@RestController
public class Example1Controller {

    @GetMapping(value = "/testExceptionHandler", produces = APPLICATION_JSON_VALUE)
    public Response testExceptionHandler(@RequestParam(required = false, defaultValue = "false") boolean exception)
            throws BusinessException {
        if (exception) {
            throw new BusinessException("BusinessException in testExceptionHandler");
        }
        return new Response("OK");
    }

    @ExceptionHandler(BusinessException.class)
    public Response handleException(BusinessException e) {
        return new Response(e.getMessage());
    }

}


testExceptionHandler, BusinessException, — . , , .



handleException . @ExceptionHandler(BusinessException.class), BusinessException. @ExceptionHandler , : @ExceptionHandler({BusinessException.class, ServiceException.class}).



— 200 JSON . , @ResponseStatus, @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR).



:





:





@ExceptionHandler , . @ExceptionHandler , , , .



HandlerExceptionResolver



HandlerExceptionResolver Spring. HandlerExceptionResolver. , , Spring . :



ExceptionHandlerExceptionResolver @ExceptionHandler, .



DefaultHandlerExceptionResolver — Spring , :



Exception HTTP Status Code
BindException 400 (Bad Request)
ConversionNotSupportedException 500 (Internal Server Error)
HttpMediaTypeNotAcceptableException 406 (Not Acceptable)
HttpMediaTypeNotSupportedException 415 (Unsupported Media Type)
HttpMessageNotReadableException 400 (Bad Request)
HttpMessageNotWritableException 500 (Internal Server Error)
HttpRequestMethodNotSupportedException 405 (Method Not Allowed)
MethodArgumentNotValidException 400 (Bad Request)
MissingServletRequestParameterException 400 (Bad Request)
MissingServletRequestPartException 400 (Bad Request)
NoSuchRequestHandlingMethodException 404 (Not Found)
TypeMismatchException 400 (Bad Request)


, REST API . . ModelAndView, , .



ResponseStatusExceptionResolver @ResponseStatus.



ServiceException:



@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public class ServiceException extends Exception {

    public ServiceException(String message) {
        super(message);
    }

}


ServiceException @ResponseStatus value INTERNAL_SERVER_ERROR, - 500.



:



@RestController
public class Example2Controller {

    @GetMapping(value = "/testResponseStatusExceptionResolver", produces = APPLICATION_JSON_VALUE)
    public Response testResponseStatusExceptionResolver(@RequestParam(required = false, defaultValue = "false") boolean exception)
            throws ServiceException {
        if (exception) {
            throw new ServiceException("ServiceException in testResponseStatusExceptionResolver");
        }
        return new Response("OK");
    }

}


GET- exception=true, 500- :





— . , @ResponseStatus .



HandlerExceptionResolver , - JSON XML . , .



:



@Component
public class CustomExceptionResolver extends AbstractHandlerExceptionResolver {

    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView modelAndView = new ModelAndView(new MappingJackson2JsonView());
        if (ex instanceof CustomException) {
            modelAndView.setStatus(HttpStatus.BAD_REQUEST);
            modelAndView.addObject("message", "CustomException was handled");
            return modelAndView;

        }
        modelAndView.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
        modelAndView.addObject("message", "Another exception was handled");
        return modelAndView;
    }

}


, . , : , ModelAndView. JSON, .



-, . . , . , — :



@RestController
public class Example3Controller {

    @GetMapping(value = "/testCustomExceptionResolver", produces = APPLICATION_JSON_VALUE)
    public Response testCustomExceptionResolver(@RequestParam(required = false, defaultValue = "false") boolean exception)
            throws CustomException {
        if (exception) {
            throw new CustomException("CustomException in testCustomExceptionResolver");
        }
        return new Response("OK");
    }

}


:





200 JSON .



@ControllerAdvice



— . Spring 3.2 @ControllerAdvice.



:



@ControllerAdvice
public class DefaultAdvice {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Response> handleException(BusinessException e) {
        Response response = new Response(e.getMessage());
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

}


, @ControllerAdvice , .

DefaultAdvice handleException. handleException @ExceptionHandler, , , . BusinessException.



: @ExceptionHandler({BusinessException.class, ServiceException.class}). @ExceptionHandler .

, handleException ResponseEntity Response:



public class Response {

    private String message;

    public Response() {
    }

    public Response(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

}


, JSON . message HttpStatus.OK, 200.



:



@RestController
public class Example4Controller {

    @GetMapping(value = "/testDefaultControllerAdvice", produces = APPLICATION_JSON_VALUE)
    public Response testDefaultControllerAdvice(@RequestParam(required = false, defaultValue = "false") boolean exception)
            throws BusinessException {
        if (exception) {
            throw new BusinessException("BusinessException in testDefaultControllerAdvice");
        }
        return new Response("OK");
    }

}


, , JSON 200:





?

! :



@ControllerAdvice(annotations = CustomExceptionHandler.class)
public class CustomAdvice {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Response> handleException(BusinessException e) {
        String message = String.format("%s %s", LocalDateTime.now(), e.getMessage());
        Response response = new Response(message);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

}


@ControllerAdvice(annotations = CustomExceptionHandler.class). CustomAdvice , @CustomExceptionHandler.



@CustomExceptionHandler :



@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomExceptionHandler {
}


:



@RestController
@CustomExceptionHandler
public class Example5Controller {

    @GetMapping(value = "/testCustomControllerAdvice", produces = APPLICATION_JSON_VALUE)
    public Response testCustomControllerAdvice(@RequestParam(required = false, defaultValue = "false") boolean exception)
            throws BusinessException {
        if (exception) {
            throw new BusinessException("BusinessException in testCustomControllerAdvice");
        }
        return new Response("OK");
    }

}


Example5Controller @CustomExceptionHandler, Example4Controller . BusinessException CustomAdvice, DefaultAdvice, .



CustomAdvice — :





. @ControllerAdvice, . .



ResponseStatusException.



ResponseStatusException:



@RestController
public class Example6Controller {

    @GetMapping(value = "/testResponseStatusException", produces = APPLICATION_JSON_VALUE)
    public Response testResponseStatusException(@RequestParam(required = false, defaultValue = "false") boolean exception) {
        if (exception) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "ResponseStatusException in testResponseStatusException");
        }
        return new Response("OK");
    }

}


ResponseStatusException , , . @ResponseStatus — -. , .



:





Resumen : hemos visto diferentes formas de manejar las excepciones, cada una de las cuales tiene sus propias características. Dentro de una aplicación grande, puede encontrar varios enfoques a la vez, pero debe tener mucho cuidado y tratar de no complicar demasiado la lógica de manejo de errores. De lo contrario, resultará que alguna excepción se manejará en el controlador incorrecto y la respuesta será diferente de la esperada. Por ejemplo, si la aplicación tiene varios asesores, al crear uno nuevo, debe asegurarse de que no rompa el orden existente de manejo de excepciones de controladores antiguos.

¡Así que ten cuidado y todo funcionará genial!



Enlace a fuentes del artículo




All Articles