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

我有多个用 @ControllerAdvice注释的类,每个类在。

其中一个处理 Exception的意图是,如果没有找到更具体的处理程序,就应该使用它。

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

这就是人们所期望的 Spring MVC 的行为吗?我试图模拟 Jersey 的一个模式,它评估每个 ExceptionMapper(等效组件) ,以确定它处理的声明类型与抛出的异常之间的距离,并始终使用最近的祖先。

78182 次浏览

这就是人们所期望的 Spring MVC 的行为吗?

从 Spring4.3.7开始,SpringMVC 的行为如下: 它使用 HandlerExceptionResolver实例来处理处理程序方法抛出的异常。

默认情况下,web MVC 配置注册一个 HandlerExceptionResolver bean,一个 HandlerExceptionResolverComposite,它

委托到其他 HandlerExceptionResolvers的列表。

其他的解析器都是

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

为了解答这个问题,我们只关心 ExceptionHandlerExceptionResolver

An AbstractHandlerMethodExceptionResolver that resolves exceptions 通过 @ExceptionHandler方法。

在上下文初始化时,Spring 将为它检测到的每个带有 @ControllerAdvice注释的类生成一个 ControllerAdviceBeanExceptionHandlerExceptionResolver将从上下文中检索这些内容,并使用 AnnotationAwareOrderComparator对它们进行排序

是支持 Spring 的 OrderedOrderComparator的扩展 接口以及 @Order@Priority注释,使用 命令实例提供的订单值重写静态 定义的注释值(如果有的话)。

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

When an exception occurs, the ExceptionHandlerExceptionResolver will iterate through these ExceptionHandlerMethodResolver and use the first one that can handle the exception.

所以这里的要点是: 如果您有一个 @ControllerAdvice,其中 @ExceptionHandler对应于 Exception,在另一个 @ControllerAdvice类,其中 @ExceptionHandler对应于更具体的异常(如 IOException)之前注册,那么第一个类将被调用。正如前面提到的,您可以通过让带 @ControllerAdvice注释的类实现 @ExceptionHandler0或者用 @ExceptionHandler1或 @ExceptionHandler2注释它并给它一个适当的值来控制注册顺序。

There's a similar situation convered in the excellent "Spring MVC 中的异常处理" post on the Spring blog, in the section entitled 全局异常处理. Their scenario involves checking for ResponseStatus annotations registered on the exception class, and if present, rethrowing the exception to let the framework handle them. You might be able to use this general tactic - try to determine if there is a might be a more appropriate handler out there and rethrowing.

或者,您可以考虑其他一些异常处理策略。

Sotirios Dlimanolis 的回答非常有帮助,在进一步的调查中,我们发现,在春季3.2.4中,寻找@ControllerAdvisory 注释的代码也会检查@Order 注释是否存在,并对 ControllerAdviceBeans 列表进行排序。

没有@Order 注释的所有控制器的结果默认顺序是 Ordered# LOWEST _ PRECEDANCE,这意味着如果你有一个需要最低优先级的控制器,那么你所有的控制器都需要一个更高的顺序。

下面的示例演示了如何使用两个带有 ControllerAdvisory 和 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 ()

好好享受吧!

The order of exception handlers can be changed using the @Order annotation.

例如:

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的值可以是任意整数。

我还在文件中发现:

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。异常-

ExceptionHandlerMethod 异常处理器方法

受保护的 ServletInvocableHandlerMethod 处理器方法(处理器方法处理器方法, 例外情况)

对象的@ExceptionHandler 方法 默认实现搜索类中的方法 hierarchy of the controller first and if not found, it continues 搜索额外的@ExceptionHandler 方法 @ ControllerAdvisory 检测到 Spring 管理的 bean 参数: HandlerMethod-引发异常的方法(可能是 Null)异常-引发的异常返回: 处理 异常,或为空

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

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

也可以使用数值,如下所示

@Order(value = 100)

较低的值具有较高的优先级。默认值为 * {@code LOWEST _ PRECEDANCE } ,指示最低优先级(丢失到任何 其他 * 指定的订单值)

须处理的重要类别:

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

如果希望分离异常处理程序(比如我) ,可以使用 @Import来完成这项工作。

@ControllerAdvice
class MyCustomExceptionHandler {
...
}
@ControllerAdvice
class MyOtherCustomExceptionHandler {
...
}
@Import({MyCustomExceptionHandler.class,MyOtherCustomExceptionHandler.class})
@ControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
class ApplicationExceptionHandler{
//Generic exception handlers
}