精尽Spring MVC源码分析 - HandlerExceptionResolver 组件
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读
Spring 版本:5.2.4.RELEASE
该系列其他文档请查看:《精尽 Spring MVC 源码分析 - 文章导读》
HandlerExceptionResolver 组件
HandlerExceptionResolver 组件,处理器异常解析器,将处理器( handler )执行时发生的异常(也就是处理请求,执行方法的过程中)解析(转换)成对应的 ModelAndView 结果
回顾
先来回顾一下在 DispatcherServlet 中处理请求的过程中哪里使用到 HandlerExceptionResolver 组件,可以回到《一个请求的旅行过程》中的 DispatcherServlet 的 processHandlerException 方法中看看,如下:
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
// 移除 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
// <a> 遍历 HandlerExceptionResolver 数组,解析异常,生成 ModelAndView 对象
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
// 遍历 HandlerExceptionResolver 数组
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
// 解析异常,生成 ModelAndView 对象
exMv = resolver.resolveException(request, response, handler, ex);
// 生成成功,结束循环
if (exMv != null) {
break;
}
}
}
// <b> 情况一,生成了 ModelAndView 对象,进行返回
if (exMv != null) {
// ModelAndView 对象为空,则返回 null
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
// 没有视图则设置默认视图
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
// 设置请求中的错误消息属性
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
// <c> 情况二,未生成 ModelAndView 对象,则抛出异常
throw ex;
}
在 Spring MVC 的 DispatcherServlet 处理请求执行方法过程中,不管是否抛出异常都会进行结果处理,如果抛出了异常也需要调用该方法处理异常
可以看到,在 <a> 处会遍历所有的 HandlerExceptionResolver 异常处理器来处理,如果某一个处理器处理成功并返回 ModelAndView 对象,则直接返回
HandlerExceptionResolver 接口
org.springframework.web.servlet.HandlerExceptionResolver,异常处理器接口,代码如下:
public interface HandlerExceptionResolver {
/**
* 解析异常,转换成对应的 ModelAndView 结果
*/
@Nullable
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
HandlerExceptionResolver 接口体系的结构如下:

初始化过程
在 DispatcherServlet 的 initHandlerExceptionResolvers(ApplicationContext context) 方法,初始化 HandlerExceptionResolver 组件,方法如下:
private void initHandlerExceptionResolvers(ApplicationContext context) {
// 置空 handlerExceptionResolvers 处理
this.handlerExceptionResolvers = null;
// 情况一,自动扫描 HandlerExceptionResolver 类型的 Bean 们
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
// 情况二,获得名字为 HANDLER_EXCEPTION_RESOLVER_BEAN_NAME 的 Bean
else {
try {
HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, no HandlerExceptionResolver is fine too.
}
}
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
/**
* 情况三,如果未获得到,则获得默认配置的 HandlerExceptionResolver 类
* {@link org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver}
* {@link org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver}
* {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver}
*/
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
如果“开启”探测功能,则扫描已注册的 HandlerExceptionResolver 的 Bean 们,添加到
handlerExceptionResolvers中,默认开启如果“关闭”探测功能,则获得 Bean 名称为 "handlerExceptionResolver" 对应的 Bean ,将其添加至
handlerExceptionResolvers如果未获得到,则获得默认配置的 HandlerExceptionResolver 类,调用
getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)方法,就是从DispatcherServlet.properties文件中读取 HandlerExceptionResolver 的默认实现类,如下:org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
在 Spring Boot 中,默认配置下会走上述 1 的逻辑,handlerExceptionResolvers 有两个元素:
org.springframework.boot.autoconfigure.web.DefaultErrorAttributes:在 Spring Boot 中,逻辑比较简单,暂时忽略org.springframework.web.servlet.handler.HandlerExceptionResolverComposite:复合的 HandlerExceptionResolver 实现类

接下来会对 HandlerExceptionResolverComposite 中的这三种异常处理器进行分析
HandlerExceptionResolverComposite
org.springframework.web.servlet.handler.HandlerExceptionResolverComposite,实现 HandlerExceptionResolver、Ordered 接口,复合的 HandlerExceptionResolver 实现类
构造方法
public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {
/**
* 异常解析器数组
*/
@Nullable
private List<HandlerExceptionResolver> resolvers;
/**
* 优先级,默认最低
*/
private int order = Ordered.LOWEST_PRECEDENCE;
}
resolvers:HandlerExceptionResolver 实现类列表order:优先级,默认最低
从上面的初始化过程中可以看到,Spring Boot 默认配置下 HandlerExceptionResolverComposite 包含三个实现类:
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolverorg.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolverorg.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
resolveException
实现 resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) 方法,遍历 HandlerExceptionResolver 数组,逐个处理异常 ex,如果成功,则返回 ModelAndView 对象,方法如下:
@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;
}
AbstractHandlerExceptionResolver
org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver,实现 HandlerExceptionResolver、Ordered 接口,HandlerExceptionResolver 抽象类,作为所有 HandlerExceptionResolver 实现类的基类
构造方法
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
private static final String HEADER_CACHE_CONTROL = "Cache-Control";
/**
* 优先级,默认最低
*/
private int order = Ordered.LOWEST_PRECEDENCE;
/**
* 匹配的处理器对象的集合
*/
@Nullable
private Set<?> mappedHandlers;
/**
* 匹配的处理器类型的数组
*/
@Nullable
private Class<?>[] mappedHandlerClasses;
/**
* 防止响应缓存
*/
private boolean preventResponseCaching = false;
}
上面的这些属性在后续方法中会讲到
shouldApplyTo
shouldApplyTo(HttpServletRequest request, Object handler) 方法,判断当前 HandlerExceptionResolver 是否能应用到传入的 handler 处理器,方法如下:
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
if (handler != null) {
// <1> 如果 mappedHandlers 包含 handler 对象,则返回 true
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
// <2> 如果 mappedHandlerClasses 包含 handler 的类型,则返回 true
if (this.mappedHandlerClasses != null) {
for (Class<?> handlerClass : this.mappedHandlerClasses) {
if (handlerClass.isInstance(handler)) {
return true;
}
}
}
}
// Else only apply if there are no explicit handler mappings.
// <3> 如果 mappedHandlers 和 mappedHandlerClasses 都为空,说明直接匹配
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
- 如果
mappedHandlers包含该handler处理器对象,则返回true - 如果
mappedHandlerClasses包含该handler处理器所在类,则返回true - 如果
mappedHandlers和mappedHandlerClasses都为空,说明直接匹配
prepareResponse
prepareResponse(Exception ex, HttpServletResponse response) 方法,阻止响应缓存,方法如下:
protected void prepareResponse(Exception ex, HttpServletResponse response) {
if (this.preventResponseCaching) {
preventCaching(response);
}
}
/**
* Prevents the response from being cached, through setting corresponding
* HTTP {@code Cache-Control: no-store} header.
* @param response current HTTP response
*/
protected void preventCaching(HttpServletResponse response) {
response.addHeader(HEADER_CACHE_CONTROL, "no-store");
}
如果想要阻止响应缓存,需要设置 preventResponseCaching 为 true
resolveException
实现 resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,代码如下:
@Override
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) {
// <1> 判断是否可以应用
if (shouldApplyTo(request, handler)) {
// <1.1> 阻止缓存
prepareResponse(ex, response);
// <1.2> 执行解析异常,返回 ModelAndView 对象
ModelAndView result = doResolveException(request, response, handler, ex);
// <1.3> 如果 ModelAndView 对象非空,则打印日志
if (result != null) {
// Print debug message when warn logger is not enabled.
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
}
// Explicitly configured warn logger in logException method.
logException(ex, request);
}
// <1.4> 返回执行结果
return result;
}
// <2> 不可应用,直接返回 null
else {
return null;
}
}
@Nullable
protected abstract ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
调用
shouldApplyTo(HttpServletRequest request, Object handler)方法,判断是否可以应用,如果可以应用- 调用
prepareResponse(Exception ex, HttpServletResponse response)方法,阻止缓存 - 调用
doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex)抽象方法,执行解析异常,返回 ModelAndView 对象 - 如果 ModelAndView 对象非空,则打印日志
- 返回执行结果
- 调用
不可应用,直接返回
null
AbstractHandlerMethodExceptionResolver
org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver,继承 AbstractHandlerExceptionResolver 抽象类,基于 handler 类型为 HandlerMethod 的 HandlerExceptionResolver 抽象类。
可能你会有疑惑,为什么 AbstractHandlerMethodExceptionResolver 只有一个 ExceptionHandlerExceptionResolver 子类,为什么还要做抽象呢?因为 ExceptionHandlerExceptionResolver 是基于 @ExceptionHandler 注解来配置对应的异常处理器,而如果未来我们想自定义其它的方式来配置对应的异常处理器,就可以来继承 AbstractHandlerMethodExceptionResolver 这个抽象类。
精尽Spring MVC源码分析 - HandlerExceptionResolver 组件的更多相关文章
- 精尽Spring MVC源码分析 - MultipartResolver 组件
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(一)之 HandlerAdapter
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
随机推荐
- 头秃了,使用@AutoConfigureBefore指定配置类顺序竟没生效?
持续原创输出,点击上方蓝字关注我 前言 日常工作中对于Spring Boot 提供的一些启动器可能已经足够使用了,但是不可避免的需要自定义启动器,比如整合一个陌生的组件,也想要达到开箱即用的效果. 在 ...
- vulnhub: DC 2
首先地址探测找到主机IP: root@kali:~# nmap -sn 192.168.74.139/24 Starting Nmap 7.80 ( https://nmap.org ) at 202 ...
- DIV设置滚动条在最底端
网站聊天样式,无论添加什么内容div的滚动条都显示在最底端 将div添加滚动条,给div一定的高度 <div id='up' style='height:100px; width:400px;o ...
- canvas 元素覆盖&穿透问题
给网站添加canvas动态背景.完后发现有a标签无法点击,想到是canvas覆盖了(但有些是可以的).网上查找,有解决穿透的问题,但canvas的鼠标事件会无效.后发现是定位问题. canvas样式 ...
- ASP.Net Core 3.1 使用gRPC入门指南
主要参考文章微软官方文档: https://docs.microsoft.com/zh-cn/aspnet/core/grpc/client?view=aspnetcore-3.1 此外还参考了文章 ...
- 不是程序员,代码也不能太丑!python官方书写规范:任何人都该了解的 pep8
不是程序员,代码也不能太丑!python官方书写规范:任何人都该了解的 pep8 简介:为什么要强调 书写规范 ?这其实并不关乎"美丑",而是为了 更高的效率(代码阅读.开发.维护 ...
- Python爬虫实战案例:取喜马拉雅音频数据详解
前言 喜马拉雅是专业的音频分享平台,汇集了有声小说,有声读物,有声书,FM电台,儿童睡前故事,相声小品,鬼故事等数亿条音频,我最喜欢听民间故事和德云社相声集,你呢? 今天带大家爬取喜马拉雅音频数据,一 ...
- 牛客巅峰赛S2第6场题解
牛客编程巅峰赛S2第6场 A-StringⅡ 题目 题目描述 给出一个仅包含小写字母的字符串s,你最多可以操作k次,使得任意一个小写字母变为与其相邻的小写字母(ASCII码差值的绝对值为1),请你求出 ...
- 第3.5节 丰富的Python字典操作
一. 基本概念 Python提供一种通过名称来访问其各个值的数据结构,这种数据结构称为映射(mapping).字典(dict)是Python中唯一的内置映射类型,其中的值不按顺序排列,而是存储在键下, ...
- Python中动态编译函数compile(source, filename, mode, ......)参数filename的作用是什么?
动态编译函数compile调用语法如下: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1) 其中的fi ...