我们知道,Spring Web
注解@ExceptionHandler
可以用来指定处理某类异常的控制器方法,从而在这些异常发生时,会有相应的控制器方法来处理此类异常,其定义方式如下 :
/**
* 此方法定义一个异常处理器,仅仅处理异常 DemoException , 它使用一个视图对象
* DemoExceptionHandlerView 来处理异常
* @param e
* @return
*/
@ExceptionHandler(DemoException.class)
public View handleDemoException(DemoException e) {
return new DemoExceptionHandlerView(e);
}
另外,通过@ExceptionHandler
定义异常处理控制器方法,又可以分为两类 :
- 定义在某个控制类内
这种情况下,所定义的异常处理控制器方法仅仅覆盖当前控制器类内各个方法所发生的这类异常 - 结合
@ControllerAdvice
使用,定义在@ControllerAdvice
注解的类内
这种情况下,所定义的异常处理控制器方法可用于@ControllerAdvice
注解覆盖的所有控制器类方法内所发生的这类异常
那么,在这种现象背后,Spring MVC
又是如何实现的呢 ? 实际上,这主要是 ExceptionHandlerExceptionResolver
在起作用 :
-
首先,
ExceptionHandlerExceptionResolver
是Spring MVC
缺省被启用的一个HandlerExceptionResolver
,它会被作为一个组合模式HandlerExceptionResolver bean
中的一个元素进入到bean
容器中。关于该知识点,可以参考:
Spring MVC : WebMvcConfigurationSupport 中定义的 HandlerExceptionResolver 组件
ExceptionHandlerExceptionResolver
实现了接口InitializingBean
,所以它在实例化时会被初始化。该过程中,它就会搜集所有的@ControllerAdvice
注解类中使用@ExceptionHandler
定义的异常处理控制器方法以供随后工作时使用。
-
接下来,
DispatcherServlet
初始化时,会搜集所有HandlerExceptionResolver bean
记录到自己的策略组件属性List<HandlerExceptionResolver> handlerExceptionResolvers
。关于这一知识点,可以参考 :
Spring MVC DispatcherServlet 策略初始化 – initHandlerExceptionResolvers -
然后,处理某个请求时某个异常发生了。
DispatcherServlet
会遍历handlerExceptionResolvers
中每个HandlerExceptionResolver
对象试图对该异常进行处理。
// DispatcherServlet 代码片段,
// 在 HandlerAdaptor 执行 Handler 之后调用,
// 如果之前的逻辑有异常,则 exception 不为 null,
// 如果之前的逻辑执行正常, 则 exception 为 null
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
// 请求处理出现了异常
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 这里视图处理该异常:
// 1. 内部将其完全处理
// 2. 或者返回一个用于渲染错误的数据模型和视图
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// ... 省略其他代码
}
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
// 遍历`handlerExceptionResolvers`中每个`HandlerExceptionResolver`对象
// 试图对异常 ex 进行处理
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
// ...
return exMv;
}
throw ex;
}
这里,轮到ExceptionHandlerExceptionResolver
处理异常时,#resolveException
最终会调用ExceptionHandlerExceptionResolver#doResolveHandlerMethodException
。该处理过程主要步骤如下 :
- 找到能处理该异常的控制器方法 –
ExceptionHandlerExceptionResolver#getExceptionHandlerMethod
;- 先从发生异常的控制器方法所在类查找是否存在使用注解
@ExceptionHandler
并能处理该异常的方法; - 如果找不到,从所有
@ControllerAdvice
注解类中查找使用注解@ExceptionHandler
并能处理该异常的方法;
- 先从发生异常的控制器方法所在类查找是否存在使用注解
- 执行处理该异常的控制器方法处理该异常;
更详细的源码分析,这里就不再展开了,可以参考下面这篇文章 :
Spring MVC : 工具 ExceptionHandlerExceptionResolver
参考文章
- Spring MVC 概念模型 – 接口 HandlerExceptionResolver
- Spring MVC : 工具 DefaultHandlerExceptionResolver
- Spring MVC : 工具 ResponseStatusExceptionResolver
- Spring MVC : 工具 ExceptionHandlerExceptionResolver
- Spring MVC : 工具 SimpleMappingExceptionResolver
- Spring MVC : WebMvcConfigurationSupport 中定义的 HandlerExceptionResolver 组件
- Spring MVC DispatcherServlet 策略初始化 – initHandlerExceptionResolvers