设置多个@ControllerAdvice @ExceptionHandlers的优先级


82

我有多个用注释的类@ControllerAdvice,每个类都有一个@ExceptionHandler方法。

一个处理程序Exception的意图是,如果找不到更多特定的处理程序,则应使用它。

遗憾的是,Spring MVC似乎总是使用最通用的情况(Exception),而不是使用更具体的情况(IOException例如)。

这是人们期望Spring MVC表现的方式吗?我正在尝试模仿Jersey的模式,该模式会评估每个ExceptionMapper(等效组件)以确定它处理的声明类型与引发的异常之间的距离,并且始终使用最接近的祖先。

Answers:


124

这是人们期望Spring MVC表现的方式吗?

从Spring 4.3.7开始,Spring MVC的行为如下:它使用HandlerExceptionResolver实例来处理处理程序方法引发的异常。

默认情况下,Web MVC配置会注册一个HandlerExceptionResolverbean HandlerExceptionResolverComposite,即

委托其他名单HandlerExceptionResolvers

那些其他解析器是

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

以该顺序注册。出于这个问题的目的,我们只关心ExceptionHandlerExceptionResolver

AbstractHandlerMethodExceptionResolver,通过解析异常@ExceptionHandler的方法。

在上下文初始化时,Spring将为它检测到的ControllerAdviceBean每个带@ControllerAdvice注释的类生成一个。该ExceptionHandlerExceptionResolver会从上下文检索这些,并使用排序,使用AnnotationAwareOrderComparator

是对扩展的OrderComparator支持,它支持Spring的Ordered 接口以及@Order@Priority批注,其中由Ordered实例提供的订单值将覆盖静态定义的批注值(如果有)。

然后,ExceptionHandlerMethodResolver将为每个这些ControllerAdviceBean实例注册一个(将可用@ExceptionHandler方法映射到它们打算处理的异常类型)。最后,将它们以相同的顺序添加到中LinkedHashMap(保留迭代顺序)。

当发生异常时,ExceptionHandlerExceptionResolver会遍历这些异常ExceptionHandlerMethodResolver并使用可以处理异常的第一个异常。

因此,这里的一点是:如果你有一个@ControllerAdvice带有@ExceptionHandler用于Exception该被另一注册前@ControllerAdvice与类@ExceptionHandler的更具体的例外,比如IOException,是第一个将被调用。如前所述,您可以通过@ControllerAdvice实现Ordered带注释的类或用@Order或对其进行注释@Priority并为其指定适当的值来控制该注册顺序。


5
此外,在中有多个@ExceptionHandler方法的情况下@ControllerAdvice,将选择一个处理抛出的异常的最特定超类的方法。
Vijay Aggarwal

在春季启动2.3.3中,不需要在子类上使用@Order注释,该子类从父控制器建议类覆盖控制器建议ExceptionHandler方法
Vadiraj Purohit

92

Sotirios Delimanolis的回答非常有帮助,在进一步调查中我们发现,无论如何在3.2.4春季,寻找@ControllerAdvice批注的代码还会检查@Order批注的存在并对ControllerAdviceBeans列表进行排序。

没有@Order批注的所有控制器的默认默认顺序为Ordered#LOWEST_PRECEDENCE,这意味着,如果您有一个控制器的优先级最低,那么所有控制器的顺序都必须更高。

这是一个示例,该示例显示如何具有两个带有ControllerAdvice和Order批注的异常处理程序类,它们可以在出现UserProfileException或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() {
        ....
    }
}
  • 请参见ControllerAdviceBean#initOrderFromBeanType()
  • 请参见ControllerAdviceBean#findAnnotatedBeans()
  • 请参见ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache()

请享用!


21

可以使用@Order注释来更改异常处理程序的顺序。

例如:

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 {

    //...

}

@Order的值可以是任何整数。


5

我还在文档中发现:

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

受保护的ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod,Exception exception)

查找给定异常的@ExceptionHandler方法。默认实现首先在控制器的类层次结构中搜索方法,如果未找到,则假定检测到某些@ControllerAdvice Spring托管bean,它将继续搜索其他@ExceptionHandler方法。参数:handlerMethod-引发异常的方法(可以为null)exception-引发的异常返回:处理异常的方法,或者为null

因此,这意味着如果您想解决此问题,则需要在引发这些异常的控制器内添加特定的异常处理程序。和定义一个和唯一的ControllerAdvice处理全局默认异常处理程序。

这简化了过程,我们不需要Order注释来处理问题。


2

在Spring博客标题为Global Exception Handling的小节中出色的“ Spring MVC中的异常处理”帖子中也存在类似的情况。他们的场景涉及检查在异常类上注册的ResponseStatus批注,如果存在,则重新抛出异常以使框架处理它们。您可能可以使用这种通用策略-尝试确定是否可能存在更合适的处理程序并重新抛出。

另外,您还可以查看其他一些异常处理策略。


1

要处理的重要类:

**@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);
    }
}

低优先级的其他例外

@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());
    }
    }

0

您还可以使用数字值,如下所示

@Order(value = 100)

较低的值具有较高的优先级。缺省值为* {@code Ordered.LOWEST_PRECEDENCE},指示最低优先级(输给任何其他*指定的订购值)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.