如何确保 @ExceptionHandler(Exception.class) 在 Spring Boot 中最后被调用?

Posted

技术标签:

【中文标题】如何确保 @ExceptionHandler(Exception.class) 在 Spring Boot 中最后被调用?【英文标题】:How to make sure that @ExceptionHandler(Exception.class) will be called last in Spring Boot? 【发布时间】:2021-12-08 22:07:42 【问题描述】:

我有一个控制器处理一些请求:

@RequestMapping(method = RequestMethod.POST, value = "/endpoint")
public ResponseEntity<MyResponse> myEndpoint(@RequestBody MyRequest request) throws Exception 
    //...

这样的控制器可能会抛出几个异常,为此我使用@ExceptionHandler这种方式:

@ExceptionHandler(SomeSpecificException.class)
@ResponseBody
public ResponseEntity<Error> handleSomeSpeficicFailure(SomeSpecificException e) 
    //handle specific exception


@ExceptionHandler(SomeOtherSpecificException.class)
@ResponseBody
public ResponseEntity<Error> handleSomeOtherSpeficicFailure(SomeOtherSpecificException e) 
    //handle specific exception


//etc.

如果抛出的异常不属于任何已知类,我添加了一个通用的Exception.class 处理程序,它返回一个自定义的 500:

@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<Error> handleUnknownFailure(Exception e) 
    //handle unknown exception

做了一些测试,它似乎工作正常。如果我抛出一个特定的异常,我会在特定的处理程序上调用,如果我抛出一个未映射的异常,我会在通用处理程序上调用。

但是,我没有看到任何关于保证首先调用特定方法然后调用通用方法的保证(JavaDoc 和 Spring 文档中都没有)。

如果 Spring 正在测试一个特定的异常是 instanceof Exception,那将是真的,所以它甚至可能首先在这个处理程序上调用我而不检查其他的。

我的问题是:

有谁知道添加@ExceptionHandler(Exception.class) 来处理一般异常是否是个好习惯?如果没有,正确的做法是什么? 我怎样才能保证类层次结构会得到尊重(例如,如果有一天我有SomeVerySpecificException extends SomeSpecificException,Spring 怎么知道它必须在SomeSpecificException 上调用我 - 直接父级 - 在Exception 之前- 祖父母)?

【问题讨论】:

【参考方案1】:

经过调试,我在他们的代码中找到了答案——尽管没有记录,但很遗憾。

@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) 
    List<Class<? extends Throwable>> matches = new ArrayList<>();
    for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) 
        if (mappedException.isAssignableFrom(exceptionType)) 
            matches.add(mappedException);
        
    
    if (!matches.isEmpty()) 
        matches.sort(new ExceptionDepthComparator(exceptionType));
        return this.mappedMethods.get(matches.get(0));
    
    else 
        return null;
    

所以基本上对于给定的Exception,他们首先查找mappedException.isAssignableFrom(exceptionType) 对应的所有映射方法。

一旦创建了这个列表,那么:

如果列表为空,则返回null,并让默认错误处理 如果只包含一个元素,则返回它 如果包含多个元素,它们会按深度(从最近到最远的扩展)对它们进行排序并返回第一个方法。

所以保证不是合同给出的,而是在执行中,看起来确实做得很好。

【讨论】:

以上是关于如何确保 @ExceptionHandler(Exception.class) 在 Spring Boot 中最后被调用?的主要内容,如果未能解决你的问题,请参考以下文章

如何覆盖 $exceptionHandler 实现

如何在@ExceptionHandler(Spring REST)中获取@RequestBody

如何使用@ExceptionHandler 捕获 HTTP 405“不允许的方法”异常?

为啥异常在 $exceptionHandler 函数中没有属性以及如何解决此限制? DataCloneError [重复]

IErrorHandler

Spring Async 最佳实践:ExceptionHandler