spring-mvc - manejo - spring exception handle
Configuración de precedencia de múltiples @ControllerAdvice @ExceptionHandlers (5)
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
-
ExceptionHandlerExceptionResolver
-
ResponseStatusExceptionResolver
-
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 interfazOrdered
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.
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.
Sotirios Delimanolis fue muy útil en su respuesta, en una investigación posterior encontramos que, de todos modos, en la primavera 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<ErrorResponse> handleUserProfileException() {
....
}
}
@ControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
class DefaultExceptionHandler {
@ExceptionHandler(RuntimeException)
@ResponseBody
ResponseEntity<ErrorResponse> handleRuntimeException() {
....
}
}
- Ver ControllerAdviceBean # initOrderFromBeanType ()
- Ver ControllerAdviceBean # findAnnotatedBeans ()
- Consulte ExceptionHandlerExceptionResolver # initExceptionHandlerAdviceCache ()
¡Disfrutar!
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.