Configuración de precedencia de múltiples @ControllerAdvice @ExceptionHandlers

Tengo clases de multiplicador anotadas con @ControllerAdvice , cada una con un método @ExceptionHandler en.

Se maneja Exception con la intención de que si no se encuentra un controlador específico, se debe usar.

Tristemente Spring MVC parece estar siempre usando el caso más genérico ( Exception ) en lugar de los más específicos ( IOException por ejemplo).

¿Es así como uno esperaría que Spring MVC se comportara? Intento emular un patrón de Jersey, que evalúa cada ExceptionMapper (componente equivalente) para determinar qué tan lejos se maneja el tipo declarado que maneja de la excepción que se ha lanzado, y siempre usa el ancestro más cercano.

¿Es así como uno esperaría que Spring MVC se comportara?

A partir de Spring 4.3.7, así es como se comporta Spring MVC: utiliza instancias de HandlerExceptionResolver para manejar las excepciones lanzadas por los métodos del manejador.

De forma predeterminada, la configuración web MVC registra un solo bean HandlerExceptionResolverComposite , HandlerExceptionResolverComposite , que

delega a una lista de otros HandlerExceptionResolvers .

Esos otros resolutores son

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

registrado en ese orden. A los fines de esta pregunta, solo nos importa ExceptionHandlerExceptionResolver .

Un AbstractHandlerMethodExceptionResolver que resuelve excepciones mediante métodos @ExceptionHandler .

En la inicialización del contexto, Spring generará un ControllerAdviceBean para cada clase anotada @ControllerAdvice que detecte. El ExceptionHandlerExceptionResolver los recuperará del contexto y los ordenará utilizando AnnotationAwareOrderComparator que

es una extensión de OrderComparator que admite la interfaz Ordered de Spring, así como las anotaciones @Order y @Priority , con un valor de orden proporcionado por una instancia ordenada que anula un valor de anotación estáticamente definido (si lo hay).

A continuación, registrará un ExceptionHandlerMethodResolver para cada una de estas instancias de ControllerAdviceBean (asignando los métodos @ExceptionHandler disponibles a los tipos de excepción que deben manejar). Estos finalmente se agregan en el mismo orden a un LinkedHashMap (que conserva el orden de iteración).

Cuando se produce una excepción, ExceptionHandlerExceptionResolver iterará a través de estos ExceptionHandlerMethodResolver y usará el primero que pueda manejar la excepción.

El punto aquí es: si tiene @ControllerAdvice con @ExceptionHandler for Exception que se registra antes de otra clase @ControllerAdvice con @ExceptionHandler para una excepción más específica, como IOException , se llamará a la primera. Como se mencionó anteriormente, puede controlar esa orden de registro haciendo que su clase anotada @ControllerAdvice implemente Ordered o @Order con @Order o @Priority y dándole un valor apropiado.

Sotirios Delimanolis fue muy útil en su respuesta, en una investigación posterior encontramos que, de todos modos, en la spring 3.2.4, el código que busca las anotaciones @ControllerAdvice también comprueba la presencia de anotaciones @Order y ordena la lista de ControllerAdviceBeans.

El orden predeterminado resultante para todos los controladores sin la anotación @Order es Ordenado # LOWEST_PRECEDENCE, lo que significa que si tiene un controlador que necesita ser la prioridad más baja, entonces TODOS los controladores deben tener un orden superior.

Aquí hay un ejemplo que muestra cómo tener dos clases de controlador de excepciones con ControllerAdvice y anotaciones de pedido que pueden servir respuestas apropiadas cuando se produce una excepción de perfil de usuario o una excepción de tiempo de ejecución.

 class UserProfileException extends RuntimeException { } @ControllerAdvice @Order(Ordered.HIGHEST_PRECEDENCE) class UserProfileExceptionHandler { @ExceptionHandler(UserProfileException) @ResponseBody ResponseEntity handleUserProfileException() { .... } } @ControllerAdvice @Order(Ordered.LOWEST_PRECEDENCE) class DefaultExceptionHandler { @ExceptionHandler(RuntimeException) @ResponseBody ResponseEntity handleRuntimeException() { .... } } 
  • Ver ControllerAdviceBean # initOrderFromBeanType ()
  • Ver ControllerAdviceBean # findAnnotatedBeans ()
  • Consulte ExceptionHandlerExceptionResolver # initExceptionHandlerAdviceCache ()

¡Disfrutar!

El orden de los manejadores de excepciones se puede cambiar usando la anotación @Order .

Por ejemplo:

 import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.ControllerAdvice; @ControllerAdvice @Order(Ordered.HIGHEST_PRECEDENCE) public class CustomExceptionHandler { //... } 

El valor de @Order puede ser cualquier número entero.

Hay una situación similar en la excelente publicación ” Exception Handling in Spring MVC ” en el blog de Spring, en la sección titulada Global Exception Handling . Su escenario implica verificar las anotaciones de ResponseStatus registradas en la clase de excepción, y si están presentes, volver a lanzar la excepción para permitir que el marco las maneje. Es posible que pueda utilizar esta táctica general: trate de determinar si existe un controlador más apropiado y reiniciando.

Alternativamente, hay otras estrategias de manejo de excepciones cubiertas que puede ver en su lugar.

También encontré en la documentación que:

ExceptionHandlerMethod

protected ServletInvocableHandlerMethod getExceptionHandlerMethod (HandlerMethod handlerMethod, Exception exception)

Encuentre un método @ExceptionHandler para la excepción especificada. La implementación predeterminada busca métodos en la jerarquía de clases del controlador primero y si no se encuentra, continúa buscando métodos @ExceptionHandler adicionales suponiendo que se detectaron algunos beans @ControllerAdvice Spring-managed . Parámetros: handlerMethod: el método donde se produjo la excepción (puede ser nula) excepción: la excepción planteada Devuelve: un método para manejar la excepción, o nulo

Entonces, esto significa que si quiere resolver este problema, deberá agregar su controlador de excepción específico dentro del controlador lanzando esas excepciones. Y para definir uno y solo ControllerAdvice que maneja el manejador global de excepciones por defecto.

Esto simplifica el proceso y no necesitamos la anotación de orden para manejar el problema.