【问题标题】:@ExceptionHandler for Error gets called only if there's no mapping for Exception仅当没有异常映射时,才会调用 @ExceptionHandler for Error
【发布时间】:2017-03-13 17:11:06
【问题描述】:

使用spring-web-4.2.6,我有以下Controller和ExceptionHandler:

@ControllerAdvice
public class ExceptionsHandler {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorDTO> HandleDefaultException(Exception ex) {
    ...
    }

    @ExceptionHandler(InternalError.class)
    public ResponseEntity<ErrorDTO> HandleInternalError(InternalError ex) {
    ...
    }
}

@RestController
@RequestMapping("/myController")
public class MyController {
    @RequestMapping(value = "/myAction", method = RequestMethod.POST)
    public boolean myAction() {
        throw new InternalError("");
    }
}

由于某种原因,调用了 ExceptionsHandler 的 HandleDefaultException(用于 Exception.class)方法,但出现了 NestedServletException 类型的异常,而不是 HandleInternalError 调用。

删除默认调用时,调用 IntenalError 调用时会出现适当的 InternalError 异常。

我不想删除默认调用,因为拥有一个默认处理程序以便为我的用户提供更好的体验对我来说很重要。

我在这里错过了什么?

编辑:

显然我正在使用 spring-web-4.3.3,没有要求它。我不明白为什么,这是我的 Gradle 依赖树:http://pastebin.com/h6KXSyp2

【问题讨论】:

  • 您确定您使用的是 4.2.6 吗?您描述的行为应仅适用于 4.3+。

标签: java spring spring-mvc exception exception-handling


【解决方案1】:

Spring MVC 应该只表现出您在 4.3 及更高版本中描述的行为。见this JIRA issue。以前,Spring MVC 不会将任何 Throwable 值暴露给 @ExceptionHandler 方法。见


从 4.3 开始,Spring MVC 将捕获从您的处理程序方法抛出的任何 Throwable 并将其包装在 NestedServletException 中,然后它将暴露给普通的 ExceptionHandlerExceptionResolver 进程。

以下是其工作原理的简短说明:

  1. 检查处理程序方法的@Controller 类是否包含任何@ExceptionHandler 方法。
  2. 如果是这样,尝试解析一个可以处理Exception 类型(包括NestedServletException)的问题。如果可以,它会使用它(如果找到多个匹配项,则会进行一些排序)。如果它不能,并且Exception 有一个cause,它会解开并再次尝试为其查找处理程序。 cause 现在可能是 Throwable(或其任何子类型)。
  3. 如果没有。它获取所有@ControllerAdvice 类并尝试在其中找到Exception 类型(包括NestedServletException)的处理程序。如果可以,它会使用它。如果不能,并且Exception 有一个cause,它会解开它并使用Throwable 类型再次尝试。

在您的示例中,您的 MyController 会引发 InternalError。由于这不是 Exception 的子类,因此 Spring MVC 将其包装在 NestedServletException 中。

MyController 没有任何 @ExceptionHandler 方法,因此 Spring MVC 跳过它。你有一个 @ControllerAdvice 注释类,ExceptionsHandler,所以 Spring MVC 会检查它。 @ExceptionHandler 注解的HandleDefaultException 方法可以处理Exception,所以Spring MVC 选择它来处理NestedServletException

如果删除 HandleDefaultException,Spring MVC 将找不到可以处理 Exception 的内容。然后它将尝试解开NestedServletException 并检查其cause。然后它会找到可以处理InternalErrorHandleInternalError

这不是一个容易处理的问题。以下是一些选项:

创建一个处理NestedServletException@ExceptionHandler 并自己检查InternalError

@ExceptionHandler(NestedServletException.class)
public ResponseEntity<String> HandleNested(NestedServletException ex) {
    Throwable cause = ex.getCause();
    if (cause instanceof InternalError) {
        // deal with it
    } else if (cause instanceof OtherError) {
        // deal in some other way
    }
}

这很好,除非您要处理一堆不同的 ErrorThrowable 类型。 (请注意,如果您不能或不知道如何处理它们,则可以重新抛出它们。Spring MVC 将默认为其他一些行为,可能会返回 500 错误代码。)

或者,您可以利用 Spring MVC 首先检查 @Controller(或 @RestController)类的 @ExceptionHandler 方法这一事实。只需将 InternalError@ExceptionHandler 方法移动到控制器中即可。

@RestController
@RequestMapping("/myController")
public class MyController {
    @RequestMapping(value = "/myAction", method = RequestMethod.POST)
    public boolean myAction() {
        throw new InternalError("");
    }

    @ExceptionHandler(value = InternalError.class)
    public ResponseEntity<String> HandleInternalError(InternalError ex) {
         ...
    }
}

现在 Spring 将首先尝试在 MyController 中查找 NestedServletException 的处理程序。它不会找到任何东西,所以它会打开NestedServletException 并得到一个InternalError。它将尝试找到InternalError 的处理程序并找到HandleInternalError

这有一个缺点,如果多个控制器的处理程序方法抛出InternalError,您必须为每个控制器添加一个@ExceptionHandler。这也可能是一个优势。您的处理逻辑将更接近引发错误的事物。

【讨论】:

  • 显然我正在使用 spring-web-4.3.3,没有要求它。我不明白为什么,这是我的 Gradle 依赖树:pastebin.com/h6KXSyp2
  • 假设我使用的是 4.3.3,我是否可以使用 @ControllerAdvice 创建两个类,一个用于特定异常(例如 InternalError),一个用于默认异常,并首先触发具有特定异常的一个?
  • 我最终创建了两个@ControllerAdvice 类,其中@Order(1) 用于特定异常,而Order(2) 用于一般异常。
猜你喜欢
  • 1970-01-01
  • 2023-03-27
  • 2012-04-28
  • 1970-01-01
  • 2019-10-13
  • 1970-01-01
  • 2020-12-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多