@ExceptionHandler for Error gets called only if there's no mapping for Exception

泄露秘密 提交于 2019-12-06 02:23:49
Sotirios Delimanolis

Spring MVC should only exhibit the behavior you describe with version 4.3 and above. See this JIRA issue. Previously, Spring MVC would not expose any Throwable values to @ExceptionHandler methods. See


Since 4.3, Spring MVC will catch any Throwable thrown from your handler methods and wrap it in a NestedServletException, which it will then expose to the normal ExceptionHandlerExceptionResolver process.

Here's a short description of how it works:

  1. Checks if the handler method's @Controller class contains any @ExceptionHandler methods.
  2. If it does, tries to resolve one that can handle the Exception type (including NestedServletException). If it can, it uses that (there's some sorting if multiple matches are found). If it can't, and the Exception has a cause, it unwraps and tries again to find a handler for that. That cause might now be a Throwable (or any of its subtypes).
  3. If it doesn't. It gets all the @ControllerAdvice classes and tries to find a handler for the Exception type (including NestedServletException) in those. If it can, it uses that. If it can't, and the Exception has a cause, it unwraps it and tries again with that Throwable type.

In your example, your MyController throws an InternalError. Since this is not a subclass of Exception, Spring MVC wraps it in an NestedServletException.

MyController doesn't have any @ExceptionHandler methods, so Spring MVC skips it. You have a @ControllerAdvice annotated class, ExceptionsHandler, so Spring MVC checks that. The @ExceptionHandler annotated HandleDefaultException method can handle Exception, so Spring MVC chooses it to handle the NestedServletException.

If you remove that HandleDefaultException, Spring MVC won't find something that can handle Exception. It will then attempt to unwrap the NestedServletException and check for its cause. It'll then find the HandleInternalError which can handle that InternalError.

This is not an easy issue to deal with. Here are some options:

Create an @ExceptionHandler that handles NestedServletException and do the check for InternalError yourself.

@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
    }
}

This is fine unless there's a bunch of different Error or Throwable types you want to handle. (Note that you can rethrow these if you can't or don't know how to handle them. Spring MVC will default to some other behavior, likely returning a 500 error code.)

Alternatively, you can take advantage of the fact that Spring MVC first checks the @Controller (or @RestController) class for @ExceptionHandler methods first. Just move the @ExceptionHandler method for InternalError into the controller.

@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) {
         ...
    }
}

Now Spring will first attempt to find a handler for NestedServletException in MyController. It won't find any so it will unwrap NestedServletException and get an InternalError. It will try to find a handler for InternalError and find HandleInternalError.

This has the disadvantage that if multiple controllers' handler methods throw InternalError, you have to add an @ExceptionHandler to each. This might also be an advantage. Your handling logic will be closer to the thing that throws the error.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!