异常处理

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

@Controller
public class SimpleController {

  // ...

  @ExceptionHandler
  public ResponseEntity<String> handle(IOException ex) {
    // ...
  }
}

异常可能与传播的顶级异常匹配(例如直接抛出的 IOException),或者与包装异常内的嵌套原因匹配 (例如包装在 IllegalStateException 内的 IOException)。这可以在任意原因级别上匹配,而之前只考虑了直接原因。

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

另外,注解声明可以缩小匹配的异常类型,如下例所示:

@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
  // ...
}

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

@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
  // ...
}

这种方式允许你将多个特定的异常类型映射到同一个处理方法。在这个方法中,你可以编写通用的逻辑来处理这些异常, 或者根据异常的具体类型进一步区分处理。这种方法特别有用,当你需要对一组相关的异常进行统一处理时。 通过这种方式,你可以简化异常处理逻辑,并提高代码的可维护性。

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

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

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

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

在多 @ControllerAdvice 安排中,我们建议在优先级相应的顺序上声明你主要的根异常映射。虽然根异常匹配比原因更受青睐, 但这是在给定控制器或 @ControllerAdvice 类的方法中定义的。这意味着在更高优先级的 @ControllerAdvice bean 上的原因匹配比在较低优先级的 @ControllerAdvice bean 上的任何匹配(例如,根)更受青睐。

最后但同样重要的是,@ExceptionHandler 方法的实现可以选择通过以其原始形式重新抛出它来处理给定的异常实例。 这在你对根级别匹配或在无法静态确定的特定上下文中的匹配感兴趣的情况下很有用。重新抛出的异常通过剩余的解析链传播, 就好像给定的 @ExceptionHandler 方法最初没有匹配一样。

Web MVC 中对 @ExceptionHandler 方法的支持建立在 DispatcherHandler 级别, HandlerExceptionHandler 机制上。

方法参数

@ExceptionHandler 方法支持以下的参数:

方法参数 描述

Exception type

For access to the raised exception.

HandlerMethod

访问引发异常的控制器方法.

RequestContext

当前请求上下文

HttpMethod

当前请求的请求方法

java.util.TimeZone, java.time.ZoneId

The time zone associated with the current request, as determined by a LocaleContextResolver.

java.io.OutputStream, java.io.Writer

原始响应流

java.util.Map, infra.ui.Model, infra.ui.ModelMap

用于访问错误响应的模型。始终为空。

RedirectModel

指定在重定向情况下使用的属性 — (即附加到查询字符串中)以及存储在重定向请求后临时使用的 Flash 属性。 参见 重定向属性Flash 属性

@RequestAttribute

请求 attributes. See @RequestAttribute for more details.

返回值

@ExceptionHandler 方法支持以下返回值:

返回值 描述

@ResponseBody

返回值通过 HttpMessageConverter 实例转换,并写入响应。参见 @ResponseBody

HttpEntity<B>, ResponseEntity<B>

返回值指定整个响应(包括 HTTP 头和正文)通过 HttpMessageConverter 实例转换,并写入响应。 参见 ResponseEntity

ErrorResponse

要呈现具有正文详细信息的 RFC 7807 错误响应,参见 错误响应

ProblemDetail

要呈现具有正文详细信息的 RFC 7807 错误响应,参见 错误响应

String

一个视图名称,将通过 ViewResolver 实现解析,并与隐式模型一起使用 — 通过命令对象和 @ModelAttribute 方法确定。 处理器方法也可以通过声明 Model 参数(前面描述过)以编程方式丰富模型。

View

一个 View 实例,用于渲染并与隐式模型一起使用 — 通过命令对象和 @ModelAttribute 方法确定。处理器方法也可以通过声明 Model 参数(前面描述过)以编程方式丰富模型。

java.util.Map, infra.ui.Model

要添加到隐式模型中的属性,视图名称通过 RequestToViewNameTranslator 隐式确定。

@ModelAttribute

要添加到模型中的属性,视图名称通过 RequestToViewNameTranslator 隐式确定。

注意 @ModelAttribute 是可选的。参见此表末尾的“任何其他返回值”。

ModelAndView 对象

要使用的视图和模型属性,以及可选的响应状态。

void

如果方法具有 void 返回类型(或 null 返回值),并且它还具有 MockResponseOutputStream 参数, 或者具有 @ResponseStatus 注解,则认为它已经完全处理了响应。如果控制器进行了积极的 ETaglastModified 时间戳检查 (参见 控制器 了解详细信息),也是如此。 如果没有上述任何情况,void 返回类型也可以表示 REST 控制器的“没有响应体”或 HTML 控制器的默认视图名称选择。

任何其他返回值

如果返回值不匹配上述任何一种,并且不是简单类型(由 BeanUtils#isSimpleProperty 确定), 则默认情况下,它被视为要添加到模型的模型属性。如果它是一个简单类型,则保持未解析状态。