Spring MVC 应该只表现出您在 4.3 及更高版本中描述的行为。见this JIRA issue。以前,Spring MVC 不会将任何 Throwable 值暴露给 @ExceptionHandler 方法。见
从 4.3 开始,Spring MVC 将捕获从您的处理程序方法抛出的任何 Throwable 并将其包装在 NestedServletException 中,然后它将暴露给普通的 ExceptionHandlerExceptionResolver 进程。
以下是其工作原理的简短说明:
- 检查处理程序方法的
@Controller 类是否包含任何@ExceptionHandler 方法。
- 如果是这样,尝试解析一个可以处理
Exception 类型(包括NestedServletException)的问题。如果可以,它会使用它(如果找到多个匹配项,则会进行一些排序)。如果它不能,并且Exception 有一个cause,它会解开并再次尝试为其查找处理程序。 cause 现在可能是 Throwable(或其任何子类型)。
- 如果没有。它获取所有
@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。然后它会找到可以处理InternalError的HandleInternalError。
这不是一个容易处理的问题。以下是一些选项:
创建一个处理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
}
}
这很好,除非您要处理一堆不同的 Error 或 Throwable 类型。 (请注意,如果您不能或不知道如何处理它们,则可以重新抛出它们。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。这也可能是一个优势。您的处理逻辑将更接近引发错误的事物。