
Na prática, geralmente é necessário tratar as exceções de maneira centralizada em um controlador ou mesmo em um aplicativo inteiro. Neste artigo, vamos analisar os principais recursos que o Spring Framework oferece para resolver esse problema e, usando exemplos simples, vamos ver como tudo funciona. Quem está interessado neste tópico - seja bem-vindo!
Inicialmente, antes da Primavera 3.2, as principais formas de exceções identificador em um aplicativo foram HandlerExceptionResolver e @ExceptionHandler anotação . Iremos analisá-los com mais detalhes abaixo, mas eles têm certas desvantagens. A partir da versão 3.2, apareceu a anotação @ControllerAdvice , que remove as limitações das soluções anteriores. E no Spring 5, uma nova classe ResponseStatusException foi adicionada que é muito útil para lidar com erros básicos para APIs REST .
E agora, as primeiras coisas primeiro, vamos lá!
Tratamento de exceções do 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 — -. , .
:
Resumo : Vimos diferentes maneiras de lidar com exceções, cada uma com suas próprias características. Em um aplicativo grande, você pode encontrar várias abordagens ao mesmo tempo, mas deve ter muito cuidado e tentar não complicar demais a lógica de tratamento de erros. Caso contrário, acontecerá que alguma exceção será tratada no manipulador errado e a resposta será diferente da esperada. Por exemplo, se o aplicativo tiver vários orientadores, ao criar um novo, você precisará certificar-se de que ele não interrompa a ordem existente de tratamento de exceções dos controladores antigos.
Portanto, tenha cuidado e tudo funcionará muito bem!