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

时间:2016-10-31 15:21:56

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

使用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调用。

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

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

我在这里缺少什么?

编辑:

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

1 个答案:

答案 0 :(得分:4)

Spring MVC应该只展示您在4.3及更高版本中描述的行为。见this JIRA issue。以前,Spring MVC不会向Throwable方法公开任何@ExceptionHandler值。参见

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

以下简要介绍了它的工作原理:

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

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

    创建一个处理InternalError的{​​{1}}并自行检查@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 } } 类型,否则这很好。 (请注意,如果您不能或者不知道如何处理它们,您可以重新抛出它们.Spring MVC将默认为其他一些行为,可能会返回500错误代码。)

    或者,您可以利用Spring MVC首先检查Error(或Throwable)类@Controller方法的事实。只需将@RestController的{​​{1}}方法移动到控制器中即可。

    @ExceptionHandler

    现在,Spring将首先尝试在@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) { ... } } 并获得NestedServletException。它会尝试找到MyController的处理程序并找到NestedServletException

    这有一个缺点,如果多个控制器&#39;处理程序方法抛出InternalError,你必须为每个方法添加InternalError。这也可能是一个优势。你的处理逻辑将更接近抛出错误的东西。