该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读

Spring 版本:5.2.4.RELEASE

该系列其他文档请查看:《精尽 Spring MVC 源码分析 - 文章导读》

HandlerExceptionResolver 组件

HandlerExceptionResolver 组件,处理器异常解析器,将处理器( handler )执行时发生的异常(也就是处理请求,执行方法的过程中)解析(转换)成对应的 ModelAndView 结果

回顾

先来回顾一下在 DispatcherServlet 中处理请求的过程中哪里使用到 HandlerExceptionResolver 组件,可以回到《一个请求的旅行过程》中的 DispatcherServletprocessHandlerException 方法中看看,如下:

@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 接口体系的结构如下:

初始化过程

DispatcherServletinitHandlerExceptionResolvers(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");
}
}
}
  1. 如果“开启”探测功能,则扫描已注册的 HandlerExceptionResolver 的 Bean 们,添加到 handlerExceptionResolvers 中,默认开启

  2. 如果“关闭”探测功能,则获得 Bean 名称为 "handlerExceptionResolver" 对应的 Bean ,将其添加至 handlerExceptionResolvers

  3. 如果未获得到,则获得默认配置的 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 包含三个实现类:

  1. org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
  2. org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver
  3. org.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);
}
  1. 如果 mappedHandlers 包含该 handler 处理器对象,则返回 true
  2. 如果 mappedHandlerClasses 包含该 handler 处理器所在类,则返回 true
  3. 如果 mappedHandlersmappedHandlerClasses 都为空,说明直接匹配

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");
}

如果想要阻止响应缓存,需要设置 preventResponseCachingtrue

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);
  1. 调用 shouldApplyTo(HttpServletRequest request, Object handler) 方法,判断是否可以应用,如果可以应用

    1. 调用 prepareResponse(Exception ex, HttpServletResponse response) 方法,阻止缓存
    2. 调用 doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) 抽象方法,执行解析异常,返回 ModelAndView 对象
    3. 如果 ModelAndView 对象非空,则打印日志
    4. 返回执行结果
  2. 不可应用,直接返回 null

AbstractHandlerMethodExceptionResolver

org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver,继承 AbstractHandlerExceptionResolver 抽象类,基于 handler 类型为 HandlerMethod 的 HandlerExceptionResolver 抽象类。

可能你会有疑惑,为什么 AbstractHandlerMethodExceptionResolver 只有一个 ExceptionHandlerExceptionResolver 子类,为什么还要做抽象呢?因为 ExceptionHandlerExceptionResolver 是基于 @ExceptionHandler 注解来配置对应的异常处理器,而如果未来我们想自定义其它的方式来配置对应的异常处理器,就可以来继承 AbstractHandlerMethodExceptionResolver 这个抽象类。

精尽Spring MVC源码分析 - HandlerExceptionResolver 组件的更多相关文章

  1. 精尽Spring MVC源码分析 - MultipartResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  2. 精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  3. 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  4. 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  5. 精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  6. 精尽Spring MVC源码分析 - HandlerAdapter 组件(一)之 HandlerAdapter

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  7. 精尽Spring MVC源码分析 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  8. 精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  9. 精尽Spring MVC源码分析 - HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

随机推荐

  1. 你还在 if...else?代码这样写才好看!

    前言 if...else 是所有高级编程语言都有的必备功能.但现实中的代码往往存在着过多的 if...else.虽然 if...else 是必须的,但滥用 if...else 会对代码的可读性.可维护 ...

  2. jenkins运行错误解决办法

    本地搭建好jenkis服务器,运行然后登陆上管理后台 构建好运行命令,然后进行构建出现如下错误说没有python运行环境 ,本地已经配置好python环境,并且终端能正常运行. 解决方法:jenkin ...

  3. SRX_Test_2_sound

    声音(sound) [问题描述] 雅礼中学的校门口在修建地铁,而由此带来的噪音问题让周边的居民困扰不已.环保局正在研究 一项评估模型,可以定量评价噪音的危害程度.这项评估模型是这样的:将每一条街道视作 ...

  4. 考研数学数一公式整理(微积分&线性代数&概率统计)

    主要根据李永乐老师的线性代数讲义.全书和汤家凤老师的高数讲义整理的. 用于记背数学需要背的公式和步骤,概念.定义.公式多,方法步骤少(毕竟太庞杂了). 本来是自用,但还是分享一下,希望有补充指正! 链 ...

  5. Apache HTTPD 换行解析漏洞--CVE-2017-15715

    CVE-2017-15715 一.漏洞描述 Apache HTTPD是一款HTTP服务器,它可以通过mod_php来运行PHP网页.其2.4.0~2.4.29版本中存在一个解析漏洞,在解析PHP时,1 ...

  6. Kafka分布式查询引擎

    1.概述 Kafka是一个分布式消息中间件系统,里面存储着实际场景中的数据.Kafka原生是不支持点查询的,如果我们想对存储在Topic中的数据进行查询,可能需要对Topic中的数据进行消费落地,然后 ...

  7. MIT-6.004计算结构(2019年春)

    L01: Introduction 略 L02: RISC-V Assembly 1.计算机处理器主要有三部分组成:内存.寄存器.算数逻辑单元 算数逻辑单元与寄存器通信,寄存器与内存通信,而算术逻辑单 ...

  8. Android的Toolbar(含溢出菜单设置[弹出菜单的使用])的使用PopMenu的样式

    http://blog.csdn.net/yingtian648/article/details/52432438(转载) 1.在Toolbar.xml中设置弹出菜单的风格(app:popupThem ...

  9. 【进阶之路】定时任务调用平台xxl-job

    大家好,我是练习java两年半时间的南橘,从一名连java有几种数据结构都不懂超级小白,到现在懂了一点点的进阶小白,学到了不少的东西.知识越分享越值钱,我这段时间总结(包括从别的大佬那边学习,引用)了 ...

  10. Java MQTT 客户端之 Paho

    Paho 自动重连后订阅的主题会清空,所以需要实现 MqttCallbackExtended 接口,在 connectComplete 方法添加订阅主题:而不是实现 MqttCallback 接口 一 ...