仅当没有 Exception 的映射时,才会调用 @ExceptionHandler for Error

Tsu*_*ury 3 java spring exception spring-mvc

使用 spring-web-4.2.6,我有以下控制器和异常处理程序:

@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("");
    }
}
Run Code Online (Sandbox Code Playgroud)

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

删除默认调用时,将使用正确的 InternalError 异常调用 IntenalError 调用。

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

我在这里缺少什么?

编辑:

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

Sot*_*lis 7

Spring MVC 应该只表现出您在 4.3 及更高版本中描述的行为。请参阅此 JIRA 问题。以前,Spring MVC 不会Throwable@ExceptionHandler方法公开任何值。看


从 4.3 开始,Spring MVC 将捕获Throwable处理程序方法中抛出的任何异常并将其包装在 a 中NestedServletException,然后将其公开给正常ExceptionHandlerExceptionResolver进程。

以下是其工作原理的简短描述:

  1. 检查处理程序方法的@Controller类是否包含任何@ExceptionHandler方法。
  2. 如果是,则尝试解析一个可以处理该Exception类型(包括NestedServletException)的类型。如果可以,它会使用它(如果找到多个匹配项,则会进行一些排序)。如果它不能,并且Exception有一个cause,它会解包并再次尝试找到一个处理程序。cause现在可能是 a (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. 然后它会找到HandleInternalError可以处理该问题的InternalError

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

创建一个@ExceptionHandler处理程序NestedServletException并自己进行检查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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

@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) {
         ...
    }
}
Run Code Online (Sandbox Code Playgroud)

NestedServletException现在 Spring 将首先尝试为in查找处理程序MyController。它找不到任何内容,因此它将解开NestedServletException并获得一个InternalError. 它将尝试找到InternalError和 find 的处理程序HandleInternalError

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