Establecimiento de la precedencia de múltiples @ControllerAdvice @ExceptionHandlers

82

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

Uno se maneja Exceptioncon la intención de que si no se encuentra un manejador más específico, este debe usarse.

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

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

EngineerBetter_DJ
fuente

Respuestas:

124

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

A partir de Spring 4.3.7, así es como se comporta Spring MVC: usa HandlerExceptionResolverinstancias para manejar excepciones lanzadas por métodos de controlador.

De forma predeterminada, la configuración de MVC web registra un solo HandlerExceptionResolverbean, a HandlerExceptionResolverComposite, que

delegados a una lista de otros HandlerExceptionResolvers.

Esos otros resolutores son

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

registrado en ese orden. A los efectos de esta pregunta solo nos preocupamos ExceptionHandlerExceptionResolver.

Una AbstractHandlerMethodExceptionResolverque resuelve excepciones a través de @ExceptionHandlermétodos.

En la inicialización del contexto, Spring generará un ControllerAdviceBeanpara cada @ControllerAdviceclase anotada que detecte. Los ExceptionHandlerExceptionResolverrecuperará del contexto y los ordenará usando el AnnotationAwareOrderComparatorque

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

Luego registrará un ExceptionHandlerMethodResolverpara cada una de estas ControllerAdviceBeaninstancias (mapeando los @ExceptionHandlermétodos disponibles a los tipos de excepción que deben manejar). Estos finalmente se agregan en el mismo orden a a LinkedHashMap(que conserva el orden de iteración).

Cuando ocurre una excepción, ExceptionHandlerExceptionResolveriterará a través de estos ExceptionHandlerMethodResolvery usará el primero que pueda manejar la excepción.

Entonces, el punto aquí es: si tiene una @ControllerAdvicecon una @ExceptionHandlerpara, Exceptionse registra antes que otra @ControllerAdviceclase con una @ExceptionHandlerpara una excepción más específica, como IOException, se llamará a la primera. Como se mencionó anteriormente, puede controlar ese orden de registro haciendo que su @ControllerAdviceclase anotada implemente Orderedo anotándola con @Ordero @Priorityy dándole un valor apropiado.

Sotirios Delimanolis
fuente
5
Además, en el caso de varios @ExceptionHandlermétodos dentro de a @ControllerAdvice, se elige el que maneja la superclase más específica de la excepción lanzada.
Vijay Aggarwal
En la primavera de arranque 2.3.3 que no requiere @Order anotación en una subclase sustituir un método ExceptionHandler consejo regulador de una clase consejos controlador de padres
Vadiraj Purohit
92

Sotirios Delimanolis fue muy útil en su respuesta, en una investigación adicional encontramos que, en la primavera 3.2.4 de todos modos, el código que busca las anotaciones @ControllerAdvice también verifica 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 Ordered # LOWEST_PRECEDENCE, lo que significa que si tiene un controlador que debe tener la prioridad más baja, TODOS sus controladores deben tener un orden superior.

A continuación, se muestra un ejemplo que muestra cómo tener dos clases de controlador de excepciones con las anotaciones ControllerAdvice y Order que pueden brindar respuestas adecuadas cuando se produce una UserProfileException o RuntimeException.

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() {
        ....
    }
}
  • Consulte ControllerAdviceBean # initOrderFromBeanType ()
  • Vea ControllerAdviceBean # findAnnotatedBeans ()
  • Consulte ExceptionHandlerExceptionResolver # initExceptionHandlerAdviceCache ()

¡Disfrutar!

Dominic Clifton
fuente
21

El orden de los manejadores de excepciones se puede cambiar mediante la @Orderanotación.

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 {

    //...

}

@OrderEl valor de puede ser cualquier número entero.

Koraktor
fuente
5

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

https://docs.spring.io/spring-framework/docs/4.3.4.RELEASE/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.html#getExceptionHandlerMethod-org.springframework. web.method.HandlerMethod-java.lang.Exception-

ExceptionHandlerMethod

protected ServletInvocableHandlerMethod getExceptionHandlerMethod (HandlerMethod handlerMethod, excepción de excepción)

Busque un método @ExceptionHandler para la excepción dada. La implementación predeterminada busca métodos en la jerarquía de clases del controlador primero y, si no se encuentran, continúa buscando métodos @ExceptionHandler adicionales asumiendo que se detectaron algunos beans administrados por @ControllerAdvice Spring . Parámetros: handlerMethod - el método donde se generó la excepción (puede ser nula) excepción - la excepción generada Devuelve: un método para manejar la excepción, o nulo

Entonces, esto significa que si desea resolver este problema, deberá agregar su controlador de excepciones específico dentro del controlador que lanza esas excepciones. ANd para definir un único ControllerAdvice que maneja el manejador de excepciones predeterminado global.

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

Mathieu Stn
fuente
2

Hay una situación similar en la excelente publicación " Manejo de excepciones en Spring MVC " en el blog de Spring, en la sección titulada Manejo de excepciones globales . Su escenario implica verificar las anotaciones ResponseStatus registradas en la clase de excepción y, si está presente, volver a lanzar la excepción para permitir que el marco las maneje. Es posible que pueda usar esta táctica general: intente determinar si hay un manejador más apropiado por ahí y volver a lanzar.

Alternativamente, hay algunas otras estrategias de manejo de excepciones cubiertas que podría considerar en su lugar.

pimlottc
fuente
1

Clase importante a manejar:

**@Order(Ordered.HIGHEST_PRECEDENCE)**
public class FunctionalResponseEntityExceptionHandler {
    private final Logger logger = LoggerFactory.getLogger(FunctionalResponseEntityExceptionHandler.class);

    @ExceptionHandler(EntityNotFoundException.class)
    public final ResponseEntity<Object> handleFunctionalExceptions(EntityNotFoundException ex, WebRequest request)
    {
        logger.error(ex.getMessage() + " " + ex);
        ExceptionResponse exceptionResponse= new ExceptionResponse(new Date(), ex.getMessage(),
                request.getDescription(false),HttpStatus.NOT_FOUND.toString());
        return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
    }
}

Otras excepciones con prioridad baja

@ControllerAdvice
    public class GlobalResponseEntityExceptionHandler extends ResponseEntityExceptionHandler
    {
    private final Logger logger = LoggerFactory.getLogger(GlobalResponseEntityExceptionHandler.class);
    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllException(Exception ex, WebRequest request)
    {
        logger.error(ex.getMessage()+ " " + ex);
        ExceptionResponse exceptionResponse= new ExceptionResponse(new Date(), ex.toString(),
                request.getDescription(false),HttpStatus.INTERNAL_SERVER_ERROR.toString());
    }
    }
Joyal Joseph
fuente
0

también puede usar un valor numérico, como a continuación

@Order(value = 100)

Los valores más bajos tienen mayor prioridad. El valor predeterminado es * {@code Ordered.LOWEST_PRECEDENCE}, lo que indica la prioridad más baja (perdiendo frente a cualquier otro * valor de pedido especificado)

Daria Yu
fuente