Exceptions

@Controller@ControllerAdvice 类可以具有 @ExceptionHandler 方法来处理控制器方法的异常,如下例所示:

@Controller and @ControllerAdvice classes can have @ExceptionHandler methods to handle exceptions from controller methods, as the following example shows: include-code::./SimpleController[indent=0]

Exception Mapping

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

The exception may match against a top-level exception being propagated (e.g. a direct IOException being thrown) or against a nested cause within a wrapper exception (e.g. an IOException wrapped inside an IllegalStateException). As of 5.3, this can match at arbitrary cause levels, whereas previously only an immediate cause was considered.

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

For matching exception types, preferably declare the target exception as a method argument, as the preceding example shows. When multiple exception methods match, a root exception match is generally preferred to a cause exception match. More specifically, the ExceptionDepthComparator is used to sort exceptions based on their depth from the thrown exception type.

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

Alternatively, the annotation declaration may narrow the exception types to match, as the following example shows:

  • 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)
}

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

You can even use a list of specific exception types with a very generic argument signature, as the following example shows:

  • 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)
}

根异常匹配和原因异常匹配之间的差异可能会令人惊讶。

The distinction between root and cause exception matching can be surprising.

在前面显示的 IOException 变体中,该方法通常使用实际的 FileSystemExceptionRemoteException 实例作为参数进行调用,因为它们都从 IOException 扩展而来。但是,如果任何这样的匹配异常在包装器异常(它本身也是 IOException)中传播,那么传入的异常实例就是该包装器异常。

In the IOException variant shown earlier, the method is typically called with the actual FileSystemException or RemoteException instance as the argument, since both of them extend from IOException. However, if any such matching exception is propagated within a wrapper exception which is itself an IOException, the passed-in exception instance is that wrapper exception.

handle(Exception) 变体中,行为甚至更简单。这种行为在包装场景中总是使用包装器异常进行调用,在这种情况下,可以通过 ex.getCause() 找到实际匹配的异常。仅当作为顶级异常抛出时,传入的异常才是实际的 FileSystemExceptionRemoteException 实例。

The behavior is even simpler in the handle(Exception) variant. This is always invoked with the wrapper exception in a wrapping scenario, with the actually matching exception to be found through ex.getCause() in that case. The passed-in exception is the actual FileSystemException or RemoteException instance only when these are thrown as top-level exceptions.

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

We generally recommend that you be as specific as possible in the argument signature, reducing the potential for mismatches between root and cause exception types. Consider breaking a multi-matching method into individual @ExceptionHandler methods, each matching a single specific exception type through its signature.

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

In a multi-@ControllerAdvice arrangement, we recommend declaring your primary root exception mappings on a @ControllerAdvice prioritized with a corresponding order. While a root exception match is preferred to a cause, this is defined among the methods of a given controller or @ControllerAdvice class. This means a cause match on a higher-priority @ControllerAdvice bean is preferred to any match (for example, root) on a lower-priority @ControllerAdvice bean.

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

Last but not least, an @ExceptionHandler method implementation can choose to back out of dealing with a given exception instance by rethrowing it in its original form. This is useful in scenarios where you are interested only in root-level matches or in matches within a specific context that cannot be statically determined. A rethrown exception is propagated through the remaining resolution chain, as though the given @ExceptionHandler method would not have matched in the first place.

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

Support for @ExceptionHandler methods in Spring MVC is built on the DispatcherServlet level, HandlerExceptionResolver mechanism.

Media Type Mapping

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

In addition to exception types, @ExceptionHandler methods can also declare producible media types. This allows to refine error responses depending on the media types requested by HTTP clients, typically in the "Accept" HTTP request header.

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

Applications can declare producible media types directly on annotations, for the same exception type: