@Controller
和 @ControllerAdvice
类可以有 @ExceptionHandler
方法来处理来自控制器方法的异常,如下例所示:
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
异常可以与正在传播的顶级异常相匹配(例如,直接抛出的 IOException),也可以与包装异常中的嵌套原因相匹配(例如,在 IllegalStateException 中包装的 IOException)。从 5.3 开始,这可以在任意的原因层次上进行匹配,而以前只考虑直接原因。
对于匹配的异常类型,最好将目标异常作为方法参数来声明,如前面的例子所示。当多个异常方法匹配时,一般来说,根部异常匹配比原因异常匹配要好。更具体地说,ExceptionDepthComparator 是用来根据异常与被抛出的异常类型的深度来排序的。
另外,注解声明可以缩小要匹配的异常类型,就像下面的例子所示:
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
// ...
}
你甚至可以使用一个特定的异常类型的列表,并使用一个非常通用的参数签名,正如下面的例子所示:
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
// ...
}
:::info 异常的根源和原因匹配之间的区别可能令人吃惊。
在前面显示的 IOException 变体中,该方法通常是以实际的 FileSystemException 或 RemoteException 实例作为参数来调用的,因为它们都是从 IOException 扩展而来的。然而,如果任何这样的匹配异常是在一个本身就是 IOException 的包装异常中传播的,那么传入的异常实例就是那个包装异常。
handle(Exception)
变量的行为更加简单。在封装的情况下,它总是和封装异常一起被调用,在这种情况下可以通过 ex.getCause()
找到实际匹配的异常。只有当 FileSystemException 或 RemoteException 作为顶层异常被抛出时,传入的异常才是实际的 FileSystemException 或 RemoteException 实例。
:::
我们通常建议你在参数签名中尽可能地具体化,以减少根源和原因异常类型之间不匹配的可能性。考虑将一个多匹配方法分解成单独的 @ExceptionHandler 方法,每个方法通过其签名匹配一个特定的异常类型。
在一个多 @ControllerAdvice 的安排中,我们建议在一个 @ControllerAdvice 上声明你的主要根异常映射,并以相应的顺序进行优先排序。虽然根异常匹配比原因更优先,但这是在给定的控制器或 @ControllerAdvice 类的方法中定义的。这意味着优先级较高的 @ControllerAdvice Bean 上的原因匹配比优先级较低的 @ControllerAdvice Bean 上的任何匹配(例如根)都要优先。
最后但并非最不重要的是,@ExceptionHandler 方法的实现可以选择退出处理一个给定的异常实例,以其原始形式重新抛出。这在你只对根级别的匹配感兴趣或者对不能静态确定的特定上下文的匹配感兴趣的情况下是很有用的。重新抛出的异常会在剩余的解析链中传播,就像给定的 @ExceptionHandler 方法在一开始就没有匹配一样。
在 Spring MVC 中对 @ExceptionHandler 方法的支持是建立在 DispatcherServlet 级别的 HandlerExceptionResolver 机制上的。
方法参数
@ExceptionHandler 方法支持以下参数:
Method argument | Description |
---|---|
Exception type | 用于访问提出的异常。 |
HandlerMethod | 用于访问引发异常的控制器方法。 |
WebRequest, NativeWebRequest | 对请求参数以及请求和会话属性的通用访问,无需直接使用 Servlet API。 |
javax.servlet.ServletRequest, javax.servlet.ServletResponse | 选择任何特定的请求或响应类型(例如,ServletRequest 或 HttpServletRequest 或 Spring 的 MultipartRequest 或 MultipartHttpServletRequest)。 |
javax.servlet.http.HttpSession | 执行一个会话的存在。因此,这样的参数永远不会是空的。 请注意,会话访问不是线程安全的。如果允许多个请求同时访问一个会话,请考虑将RequestMappingHandlerAdapter 实例的 synchronizeOnSession 标志设置为 true。 |
java.security.Principal | 目前认证的用户—如果知道的话,可能是一个特定的 Principal 实现类。 |
HttpMethod | 请求的 HTTP 方法。 |
java.util.Locale | 当前的请求语言,由最具体的 LocaleResolver 决定—实际上就是配置的 LocaleResolver 或 LocaleContextResolver。 |
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 方法支持以下返回值:
Return value | Description |
---|---|
@ResponseBody | 返回值通过 HttpMessageConverter 实例进行转换并写入响应中。参见 @ResponseBody。 |
HttpEntity, ResponseEntity | 返回值指定通过 HttpMessageConverter 实例转换完整的响应(包括 HTTP 头和正文)并写入响应中。参见 ResponseEntity。 |
String | 用 ViewResolver 实现解析的视图名称,并与隐式模型一起使用—通过命令对象和 @ModelAttribute 方法确定。处理程序方法也可以通过声明一个 Model 参数(如前所述)以编程方式丰富模型。 |
View | 一个视图实例,与隐含模型一起用于渲染—通过命令对象和 @ModelAttribute 方法确定。处理方法也可以通过声明一个 Model 参数(前面已经描述过),以编程方式丰富模型。 |
java.util.Map, org.springframework.ui.Model | 要添加到隐式模型的属性,视图名称通过 RequestToViewNameTranslator 隐式确定。 |
@ModelAttribute | 一个将被添加到模型中的属性,其视图名称通过 RequestToViewNameTranslator 隐式确定。 注意,@ModelAttribute 是可选的。参见本表末尾的 「任何其他返回值」。 |
ModelAndView object | 要使用的视图和模型属性,以及可选的响应状态。 |
void | 如果一个方法有一个 ServletResponse 或者 OutputStream 参数,或者 @ResponseStatus 注解,那么这个方法的返回类型为无效(或者返回值为空),被认为是完全处理了响应。如果控制器进行了积极的 ETag 或 lastModified 时间戳检查,也是如此(详见控制器)。 如果以上都不是真的,无效的返回类型也可以表示 REST 控制器的 「无响应体」或 HTML 控制器的默认视图名称选择。 |
Any other return value | 如果一个返回值没有与上述任何一个匹配,并且不是一个简单的类型(由BeanUtils#isSimpleProperty 决定),默认情况下,它被视为一个模型属性,被添加到模型中。如果它是一个简单的类型,它仍然是未解决的。 |
REST API 异常
REST 服务的一个常见要求是在响应体中包含错误细节。Spring 框架不会自动做到这一点,因为错误细节在响应主体中的表现是特定于应用程序的。然而,@RestController 可以使用带有 ResponseEntity 返回值的 @ExceptionHandler 方法来设置状态和响应的主体。这样的方法也可以在@ControllerAdvice 类中声明,以便全局应用。
实现全局异常处理并在响应体中提供错误细节的应用程序应考虑扩展 ResponseEntityExceptionHandler,它为 Spring MVC 引发的异常提供处理方法,并提供钩子来定制响应体。要利用这一点,需要创建一个 ResponseEntityExceptionHandler 的子类,用 @ControllerAdvice 来注解它,覆盖必要的方法,并将其声明为 Spring Bean。