En Spring, puede configurar el controlador de excepciones "global" a través de la anotación @ControllerAdvice y @ExceptionHandler. Estoy tratando de utilizar este mecanismo para tener dos controladores de excepciones globales:
RestControllerExceptionHandler , que debería devolver respuestas de error como json para cualquier controlador anotado con @RestControllerControllerExceptionHandler , que debería imprimir un mensaje de error en la pantalla para cualquier otro controlador (anotado con @Controller ) El problema es que cuando declaro estos dos manejadores de excepciones, Spring siempre usa ControllerExceptionHandler y nunca RestControllerExceptionHandler para manejar la excepción.
¿Cómo hacer que esto funcione? Por cierto: traté de usar la anotación @Order pero esto no parece funcionar.
Aquí están mis controladores de excepciones:
// should handle all exception for classes annotated with @ControllerAdvice(annotations = RestController.class) public class RestControllerExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleUnexpectedException(Exception e) { // below object should be serialized to json ErrorResponse errorResponse = new ErrorResponse("asdasd"); return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); } // should handle exceptions for all the other controllers @ControllerAdvice(annotations = Controller.class) public class ControllerExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<String> handleUnexpectedException(Exception e) { return new ResponseEntity<String>("Unexpected exception, HttpStatus.INTERNAL_SERVER_ERROR); } } } Cuando elimino ControllerExceptionHandler RestControllerExceptionHandler es llamado correctamente por Spring (solo para las clases anotadas con @RestController ).... pero cuando agrego ControllerExceptionHandler , todo pasa a través de ControllerExceptionHandler . ¿Por qué?
Después de una investigación más profunda, parece que el orden alfabético importa:/.
Cuando cambié el nombre de mi RestControllerExceptionHandler a ARestControllerExceptionHandler (que alfabéticamente precede a ControllerExceptionHandler ), ¡todo funciona como se esperaba! ARestControllerExceptionHandler maneja correctamente las excepciones de RestControllers y ControllerExceptionHandler maneja las excepciones de otros controladores.
Creé un error en primavera para esto: https://jira.spring.io/browse/SPR-15432
--- EDITAR:
Recibí la respuesta para SPR-15432 donde se sugiere que este caso se puede resolver con @Order ( org.springframework.core.annotation.Order ) o implementando la interfaz ordenada.
Esto no me funcionó antes, pero parece que he importado una anotación @Order incorrecta. (de log4j2 en lugar de primavera). Después de arreglar esto, funciona. La versión fija es la siguiente:
// should handle all exception for classes annotated with @ControllerAdvice(annotations = RestController.class) @Order(1) // NOTE: order 1 here public class RestControllerExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleUnexpectedException(Exception e) { // below object should be serialized to json ErrorResponse errorResponse = new ErrorResponse("asdasd"); return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); } } // should handle exceptions for all the other controllers @ControllerAdvice(annotations = Controller.class) @Order(2) // NOTE: order 2 here public class ControllerExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<String> handleUnexpectedException(Exception e) { return new ResponseEntity<String>("Unexpected exception, HttpStatus.INTERNAL_SERVER_ERROR); } }Eso sucede porque la anotación @RestController también es un @Controller, por lo que Spring está considerando el @ControllerAdvice con annotation = Controller.class para ambos.
Puede probar otro método para definir el subconjunto de controladores que @ControllerAdvice tendrá efecto, así que aquí hay algunas soluciones:
Cree una nueva Anotación @NotRestController:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface NotRestController { }Marque los controladores que no sean @RestController con @Controller y @NotRestController:
@Controller @NotRestController @RequestMapping("/controller") public class SampleController { } Use NotRestController.class en ControllerExceptionHandler :
@ControllerAdvice(annotations = NotRestController.class) public class ControllerExceptionHandler { Creé un proyecto de muestra con la Solution 1 y lo compartí en Github:
https://github.com/brunocleite/springexceptionhandlertest
Mueva sus clases @RestController a un paquete foo.bar y sus clases @Controller a otro paquete foo.nuk .
Use la propiedad basePackages de @ControllerAdvice en lugar de annotations :
@ControllerAdvice(basePackages={"foo.bar"}) public class RestControllerExceptionHandler { @ControllerAdvice(basePackages={"foo.nuk"}) public class ControllerExceptionHandler {¡Saludos!
Esta fue mi solución. Elimina la necesidad de crear anotaciones adicionales.
@Controller public class BaseController @Controller public class BaseRestController @Controller public class UserController extends BaseController @RestController public class UserRestController extends BaseRestController @ControllerAdvice(assignableTypes = BaseController.class) public class GlobalController @ControllerAdvice(assignableTypes = BaseRestController.class) public class GlobalRestController