异常

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

异常映射

异常可以匹配正在传播的顶级异常(例如,直接抛出的 IOException)或包装器异常中的嵌套原因(例如,包装在 IllegalStateException 中的 IOException)。从 5.3 版本开始,这可以匹配任意原因级别,而以前只考虑直接原因。

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

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

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

根异常和原因异常匹配之间的区别可能令人惊讶。 在前面所示的 IOException 变体中,方法通常以实际的 FileSystemExceptionRemoteException 实例作为参数调用,因为它们都继承自 IOException。但是,如果任何此类匹配异常在本身是 IOException 的包装器异常中传播,则传入的异常实例是该包装器异常。 handle(Exception) 变体中的行为甚至更简单。在包装场景中,它总是使用包装器异常调用,在这种情况下,实际匹配的异常可以通过 ex.getCause() 找到。传入的异常是实际的 FileSystemExceptionRemoteException 实例,仅当它们作为顶级异常抛出时。

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

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

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

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

媒体类型映射

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

应用程序可以直接在注解上声明可生成的媒体类型,对于相同的异常类型:

这里,方法处理相同的异常类型,但不会被拒绝为重复。相反,请求 "application/json" 的 API 客户端将收到 JSON 错误,浏览器将获得 HTML 错误视图。每个 @ExceptionHandler 注解可以声明多个可生成的媒体类型,错误处理阶段的内容协商将决定使用哪种内容类型。

方法参数

@ExceptionHandler 方法支持以下参数:

方法参数 描述

异常类型

用于访问抛出的异常。

HandlerMethod

用于访问抛出异常的控制器方法。

WebRequest, NativeWebRequest

泛型访问请求参数以及请求和会话属性,无需直接使用 Servlet API。

jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse

选择任何特定的请求或响应类型(例如,ServletRequestHttpServletRequest 或 Spring 的 MultipartRequestMultipartHttpServletRequest)。

jakarta.servlet.http.HttpSession

强制会话的存在。因此,此类参数永远不会为 null。请注意,会话访问不是线程安全的。如果允许多个请求并发访问会话,请考虑将 RequestMappingHandlerAdapter 实例的 synchronizeOnSession 标志设置为 true

java.security.Principal

当前认证用户——如果已知,可能是特定的 Principal 实现类。

HttpMethod

请求的 HTTP 方法。

java.util.Locale

当前请求区域设置,由最具体的 LocaleResolver 确定——实际上是配置的 LocaleResolverLocaleContextResolver

java.util.TimeZone, java.time.ZoneId

与当前请求关联的时区,由 LocaleContextResolver 确定。

java.io.OutputStream, java.io.Writer

用于访问原始响应体,由 Servlet API 公开。

java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap

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

RedirectAttributes

指定重定向时要使用的属性——(即要附加到查询字符串)和要临时存储直到重定向后的请求的 flash 属性。请参阅 重定向属性Flash 属性

@SessionAttribute

用于访问任何会话属性,与因类级别 @SessionAttributes 声明而存储在会话中的模型属性不同。有关更多详细信息,请参阅 @SessionAttribute

@RequestAttribute

用于访问请求属性。有关更多详细信息,请参阅 @RequestAttribute

返回值

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

返回值 描述

@ResponseBody

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

HttpEntity<B>, ResponseEntity<B>

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

ErrorResponse, ProblemDetail

用于呈现 RFC 9457 错误响应,正文中包含详细信息,请参阅 错误响应

String

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

View

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

java.util.Map, org.springframework.ui.Model

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

@ModelAttribute

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

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

ModelAndView 对象

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

void

具有 void 返回类型(或 null 返回值)的方法,如果它还具有 ServletResponseOutputStream 参数或 @ResponseStatus 注解,则被认为已完全处理了响应。如果控制器已进行了正向 ETaglastModified 时间戳检查,则也是如此(有关详细信息,请参阅 控制器)。

如果以上都不是,void 返回类型也可以表示 REST 控制器的“无响应体”或 HTML 控制器的默认视图名称选择。

任何其他返回值

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