异常

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

  • Java

  • Kotlin

@Controller
public class SimpleController {

	// ...

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

	// ...

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

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

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

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

  • Java

  • Kotlin

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

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

  • Java

  • Kotlin

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

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

在前面显示的 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 方法支持以下参数:

方法参数 描述

异常类型

用于访问引发的异常。

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

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

@SessionAttribute

用于访问任何会话属性,与存储在会话中的模型属性形成对比,后者是由类级别的 @SessionAttributes 声明的结果。有关更多详细信息,请参阅 @SessionAttribute

@RequestAttribute

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

Return Values

@ExceptionHandler methods support the following return values:

Return value Description

@ResponseBody

The return value is converted through HttpMessageConverter instances and written to the response. See @ResponseBody.

HttpEntity<B>, ResponseEntity<B>

The return value specifies that the full response (including the HTTP headers and the body) be converted through HttpMessageConverter instances and written to the response. See ResponseEntity.

ErrorResponse

To render an RFC 7807 error response with details in the body, see Error Responses

ProblemDetail

To render an RFC 7807 error response with details in the body, see Error Responses

String

A view name to be resolved with ViewResolver implementations and used together with the implicit model — determined through command objects and @ModelAttribute methods. The handler method can also programmatically enrich the model by declaring a Model argument (described earlier).

View

A View instance to use for rendering together with the implicit model — determined through command objects and @ModelAttribute methods. The handler method may also programmatically enrich the model by declaring a Model argument (descried earlier).

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

Attributes to be added to the implicit model with the view name implicitly determined through a RequestToViewNameTranslator.

@ModelAttribute

An attribute to be added to the model with the view name implicitly determined through a RequestToViewNameTranslator.

Note that @ModelAttribute is optional. See “Any other return value” at the end of this table.

ModelAndView object

The view and model attributes to use and, optionally, a response status.

void

A method with a void return type (or null return value) is considered to have fully handled the response if it also has a ServletResponse an OutputStream argument, or a @ResponseStatus annotation. The same is also true if the controller has made a positive ETag or lastModified timestamp check (see Controllers for details).

If none of the above is true, a void return type can also indicate “no response body” for REST controllers or default view name selection for HTML controllers.

Any other return value

If a return value is not matched to any of the above and is not a simple type (as determined by BeanUtils#isSimpleProperty), by default, it is treated as a model attribute to be added to the model. If it is a simple type, it remains unresolved.