异常处理流程
# 430.异常处理流程
书接上文,讲讲这些处理异常的组件是如何串起来的
# 准备
以 basic_table
页面为例,先屏蔽断点,debug 方式启动;
登录后,再打开断点,并点击 basic_table
的菜单,确保当前请求是 /basic_table
,开始调试分析。
# 开始
我们 debug 到执行目标方法的代码:
如果目标方法执行成功,那么就会返回 ModelAndView 对象,没有异常处理;
如果有任何异常,都会被 catch(不管是 Exception 还是 Throwable)并且赋值给 dispatchException
:
我们继续往下执行,此时由于有异常,就会执行到赋值异常的代码:
然后就会执行到 processDispatchResult
方法,这个我们在讲视图解析的时候说过,就是用来解析视图的;哪怕目标方法出现了异常,也会执行该方法。
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
我们步入进去:该方法就会判断异常是否为 null,不是则调用 processHandlerException
方法,处理完后会返回一个 ModelAndView 对象:
我们步入进去,首先定义了一个 ModelAndView
对象 exMv,用来返回:
然后是一个熟悉的 for 循环 Handler
,看谁能处理当前的异常,可以的话就直接返回(在 if
分支里,没有能处理的话,就会抛出):
默认有这些 HandlerExceptionResolver
(异常解析处理器):
ps:目前是除 0 异常,默认的异常解析处理器是处理不了的,因此会抛出。
HandlerExceptionResolver,其实就是一个接口:
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
2
3
4
5
其只有一个方法,就是根据传入的 req,resp,异常信息,来决定怎么处理异常,最后返回一个 mv 对象。因此我们也可以自定义异常解析处理器。
我们继续 for 循环,第一个就是 DefaultErrorAttributes
,其方法如下:
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
storeErrorAttributes(request, ex);
return null;
}
private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
request.setAttribute(ERROR_ATTRIBUTE, ex);
}
2
3
4
5
6
7
8
9
10
可以看到就是存储了一些错误信息,然后就返回为空了,也就是说该组件并不处理异常,只是将一些异常信息保存到 request 域中
由于返回为了空,因此继续循环,由 HandlerExceptionResolverComposite
来处理;其实该类是 3 个异常解析处理器的集合而已,其内部也是循环各个处理器,其方法如下:
@Override
@Nullable
public ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (this.resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
第一个是 ExceptionHandlerExceptionResolver
,这是用了注解 @ExceptionHandler
的时候,才会用到该解析器
第二个是 ResponseStatusExceptionResolver
,这个也是和注解使用的,就是设置响应状态码
最后也不行;因此我们目前的异常处理器,是无法处理该除 0 异常的,因此会抛出异常。
由于没有处理器能处理该异常,如果我们此时放行,下一个请求就会是/error(这是 Servlet 的规范):
而我们上一篇博客说过,/error 请求,就是由 BasicErrorController
来处理的,因此我们之前就看到了 SpringBoot 的默认错误页面。
我们简单过一遍,首先会获取 Handler,这里就是获取到了 BasicErrorController
然后就是视图解析的过程了,经过内容协商,就会返回 HTML 格式的错误页面给到浏览器(因为浏览器要的 HTML 格式):
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
2
3
4
5
6
7
8
在 resolveErrorView 方法中,就会遍历所有的 ErrorViewResolver
:
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
Map<String, Object> model) {
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
2
3
4
5
6
7
8
9
10
默认只有一个 ErrorViewResolver
,也就是我们之前说的 DefaultErrorViewResolver
:
DefaultErrorViewResolver 类中,则是这样处理的:将 HTTP 状态码拿到,然后调用 resolve 方法拼接即可,之前讲过,这里就不再重复了。
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
2
3
4
5
6
7
8
# 总结
执行目标方法,运行期间有任何异常都会被 catch,并且用赋值给 dispatchException
进入视图解析流程,执行
processDispatchResult
方法,该方法会处理异常,然后返回 ModelAndView- 遍历所有的 handlerExceptionResolvers,看谁能处理当前异常
- 系统默认的 异常解析器有几个,不过都不能处理除 0 异常,因此会抛出
由于没有处理器能处理当前异常,根据 Servlet 规范,会发送 /error 请求。该请求会被底层的 BasicErrorController 处理
然后会遍历所有的 ErrorViewResolver ,看谁能解析
会被默认的 DefaultErrorViewResolver 处理,该组件的作用是把响应状态码作为错误页的地址,error/500.html
模板引擎最终响应这个页面 error/500.html
为了后续方便测试,我们将 404.html 重命名为 4xx.html:
并修改显示内容:
<h2 th:text="${status}">page not found</h2>
<h3 th:text="${message}">We Couldn’t Find This Page</h3>
2