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

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

@ControllerAdvice public class ExceptionsHandler { @ExceptionHandler(Exception.class) public ResponseEntity HandleDefaultException(Exception ex) { ... } @ExceptionHandler(InternalError.class) public ResponseEntity 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调用。

删除默认调用时,将使用正确的InternalErrorexception调用IntenalError调用。

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

我在这里想念的是什么?

编辑:

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

Spring MVC应该只展示您在4.3及更高版本中描述的行为。 请参阅此JIRA问题 。 以前,Spring MVC不会将任何Throwable值暴露给@ExceptionHandler方法。 看到

  • ExceptionHandler不适用于Throwable

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

以下是它如何工作的简短描述:

  1. 检查处理程序方法的@Controller类是否包含任何@ExceptionHandler方法。
  2. 如果是,请尝试解析可以处理Exception类型的一个(包括NestedServletException )。 如果可以,它会使用它(如果找到多个匹配,则会有一些排序)。 如果它不能,并且Exceptioncause ,它会解包并再次尝试为其找到处理程序。 这个cause现在可能是Throwable (或其任何子类型)。
  3. 如果没有。 它获取所有@ControllerAdvice类,并尝试在其中找到Exception类型(包括NestedServletException )的处理程序。 如果可以,它会使用它。 如果它不能,并且Exceptioncause ,它会解开它并再次尝试使用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 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 HandleInternalError(InternalError ex) { ... } } 

现在Spring将首先尝试在MyControllerNestedServletException找到一个处理程序。 它将找不到任何内容,因此它将解NestedServletException并获取InternalError 。 它将尝试为InternalError找到一个处理程序并找到HandleInternalError

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