Exceptions

@Controller@ControllerAdvice 类可以具有 @ExceptionHandler 方法来处理控制器方法的异常,如下例所示: include-code::./SimpleController[indent=0]

Exception Mapping

该异常可能与正在传播的顶级异常(例如直接抛出 IOException 异常)相匹配,也可能与包装器异常(例如在 IllegalStateException 中包装 IOException 异常)内的嵌套原因相匹配。从 5.3 开始,这可以在任意原因级别处相匹配,而以前仅考虑直接原因。

对于匹配异常类型,最好将目标异常声明为方法参数,如前一个示例所示。当多个异常方法相匹配时,通常会优先考虑根异常匹配,而不是原因异常匹配。更具体地说,ExceptionDepthComparator 用于根据从抛出异常类型开始的深度对异常进行排序。

或者,注释声明可以缩小要匹配的异常类型范围,如下面的示例所示:

  • Java

  • Kotlin

@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handleIoException(IOException ex) {
	return ResponseEntity.internalServerError().body(ex.getMessage());
}
@ExceptionHandler(FileSystemException::class, RemoteException::class)
fun handleIoException(ex: IOException): ResponseEntity<String> {
	return ResponseEntity.internalServerError().body(ex.message)
}

甚至可以使用特定异常类型的列表与非常通用的参数签名结合使用,如下面的示例所示:

  • Java

  • Kotlin

@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handleExceptions(Exception ex) {
	return ResponseEntity.internalServerError().body(ex.getMessage());
}
@ExceptionHandler(FileSystemException::class, RemoteException::class)
fun handleExceptions(ex: Exception): ResponseEntity<String> {
	return ResponseEntity.internalServerError().body(ex.message)
}

根异常匹配和原因异常匹配之间的差异可能会令人惊讶。 在前面显示的 IOException 变体中,该方法通常使用实际的 FileSystemExceptionRemoteException 实例作为参数进行调用,因为它们都从 IOException 扩展而来。但是,如果任何这样的匹配异常在包装器异常(它本身也是 IOException)中传播,那么传入的异常实例就是该包装器异常。 在 handle(Exception) 变体中,行为甚至更简单。这种行为在包装场景中总是使用包装器异常进行调用,在这种情况下,可以通过 ex.getCause() 找到实际匹配的异常。仅当作为顶级异常抛出时,传入的异常才是实际的 FileSystemExceptionRemoteException 实例。

我们通常建议您在参数签名中尽可能具体,以减少根异常类型和原因异常类型之间的不匹配可能性。考虑将多匹配方法分解为各个 @ExceptionHandler 方法,每个方法都通过其签名匹配单个特定异常类型。

在多个 @ControllerAdvice 排列中,我们建议您在具有对应顺序的 @ControllerAdvice 上声明您的主要根异常映射。虽然根异常匹配优先于原因匹配,但这是在给定控制器或 @ControllerAdvice 类的所有方法之间进行的定义。这意味着优先级较高的 @ControllerAdvice Bean 上的原因匹配优先于优先级较低 @ControllerAdvice Bean 上的任何匹配(例如,根匹配)。

最后但并非最不重要的是,@ExceptionHandler 方法实现可以选择通过以原始形式重新抛出给定异常实例来退出处理该异常实例。这对于以下情况非常有用:您仅对根级别匹配或特定上下文中(该上下文无法静态确定)的匹配感兴趣。重新抛出的异常通过剩余的解析链传播,就好像给定的 @ExceptionHandler 方法从一开始就没有匹配一样。

Spring MVC 对 @ExceptionHandler 方法的支持建立在 DispatcherServlet 级别的 HandlerExceptionResolver 机制上。

Media Type Mapping

除了异常类型之外,@ExceptionHandler 方法还可以声明可生产的媒体类型。这允许根据 HTTP 客户端请求的媒体类型(通常在“Accept”HTTP 请求头中)来优化错误响应。

应用程序可以直接在注释中为同一异常类型声明可生产的媒体类型: