SpringMVC源码阅读:异常解析器
1.前言
SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧
本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC如何完成异常解析、捕捉异常,并自定义异常和异常解析器
2.源码分析
进入DispatcherServlet的processDispatchResult方法

1024行判断异常是否是ModelAndViewDefiningException类型,如果是,直接返回ModelAndView
不是ModelAndViewDefiningException类型,则获取HandlerMethod,调用processHandlerExeception方法
点进去1030行的processHandlerException方法,该方法根据HandlerExecutionResolvers来解析异常并选择ModelAndView

1217行遍历HandlerExceptionResolvers,我们讲过,在<mvc:annotation-driven/>帮我们注册了默认的异常解析器

请看AnnotationDrivenBeanDefinitionParser(解析annotation-driven的类)

1218行调用HandlerExceptionResolver的resolveException方法,该方法被子类AbstractHandlerExceptionResolver实现
1225行给request设置异常信息
现在进入HandlerExceptionResolver接口resolveException方法的实现处——AbstractHandlerExceptionResolver的resolveException方法
131行判断该异常解析器是否可以被应用到Handler
135行为异常情况准备response,即给response添加头部
136行调用抽象方法doResolveException,由子类实现
进入AbstractHandlerMethodExceptionResolver的doResolveException方法

59行调用抽象方法,被子类ExceptionHandlerExceptionResolver实现
打开ExceptionHandlerExceptionResolver的doResolveHandlerMethodException方法

362行获取有异常的Controller方法
367~368行为ServletInvocableHandlerMethod设置HandlerMethodArgumentResolverComposite和HandlerMethodReturnValueComposite,用来解析参数和处理返回值
380行调用invokeAndHandle方法处理返回值,暴露cause
384行无cause
3.实例
3.1 使用@ResponseStatus自定义异常UnauthorizedException
@ResponseStatus会被ResponseStatusExceptionResolver解析
@ResponseStatus(code=HttpStatus.UNAUTHORIZED,reason="用户未授权")
public class UnauthorizedException extends RuntimeException { }
测试方法
@RequestMapping("/unauth")
public Map unauth() {
throw new UnauthorizedException();
}
浏览器输入http://localhost:8080/springmvcdemo/error/unauth

3.2 无注解情况
测试方法
@RequestMapping("/noSuchMethod")
public Map noHandleMethod() throws NoSuchMethodException {
throw new NoSuchMethodException();
}
没有@ExceptionHandler和@ResponseStatus注解则会被DefaultHandlerExceptionResolver解析
浏览器输入http://localhost:8080/springmvcdemo/error/noSuchMethod

3.3 @ExceptionHandler处理异常
测试方法
@ExceptionHandler会被ExceptionHandlerExceptionResolver解析
@RequestMapping("/exception")
@ResponseBody
public Map exception() throws ClassNotFoundException {
throw new ClassNotFoundException("class not found");
}
@RequestMapping("/nullpointer")
@ResponseBody
public Map nullpointer() {
Map resultMap = new HashMap();
String str = null;
str.length();
resultMap.put("strNullError",str);
return resultMap;
}
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public Map error(RuntimeException error, HttpServletRequest request) {
Map resultMap = new HashMap();
resultMap.put("param", "Runtime error");
return resultMap;
}
@ExceptionHandler()
@ResponseBody
public Map error(Exception error, HttpServletRequest request, HttpServletResponse response) {
Map resultMap = new HashMap();
resultMap.put("param", "Exception error");
return resultMap;
}
浏览器输入http://localhost:8080/springmvcdemo/error/classNotFound

浏览器输入http://localhost:8080/springmvcdemo/error/nullpointer

根据异常类继承关系,ClassNotFoundException离Exception更近,所以被@ExceptionHandler()的error方法解析,注解无参相当于Exception.class。
同理,NullPointerException方法离NullPointerException“最近”,把@ExceptionHandler(NullPointerException.class)的error方法注释掉,浏览器输入http://localhost:8080/springmvcdemo/error/nullpointer,会发现
浏览器返回RuntimeException,印证了我们的说法
3.4 定义全局异常处理
/**
* @Author: 谷天乐
* @Date: 2019/1/21 10:48
* @Description: ExceptionHandlerMethodResolver内部找不到Controller的@ExceptionHandler注解的话,
* 会找@ControllerAdvice中的@ExceptionHandler注解方法
*/
@ControllerAdvice
public class ExceptionControllerAdvice { @ExceptionHandler(Throwable.class)
@ResponseBody
public Map<String, Object> ajaxError(Throwable error, HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("error", error.getMessage());
map.put("result", "error");
return map;
} }
浏览器输入http://localhost:8080/springmvcdemo/error/unauth

优先级关系:@ExceptionHandler>@ControllerAdvice中的@ExceptionHandler>@ResponseStatus
要把TestErrorController中@ExceptionHandler的方法注释掉才会有效果
4.总结
HandlerExceptionResolver作为异常解析器的接口,核心方法是resolveException
AbstractHandlerExceptionResolver实现HandlerException,resolveException方法内部调用抽象方法doResolveException,该方法被子类实现;shouldApplyTo方法检查该异常解析器是否可以被应用到Handler
AbstractHandlerMethodExceptionResolver的doResolveException内部调用抽象方法doResolveHandlerMethodException,由子类实现,返回ModelAndView,可以在视图模型里自定义错误页面;shouldApplyTo调用父类方法
ExceptionHandlerExceptionResovler的doResolveHandlerMethodException处理异常,返回ModelAndView
DefaultHandlerExceptionResolver的doResolveException处理默认异常
ResponseStatusExceptionResolver的doResolveException方法处理@ResponseStatus修饰的异常
DispatcherServlet的processHandlerException方法根据注册的HandlerExceptionResolvers选择一个ModelAndView
DispatcherServlet的doDispatch方法调用processDispatchResult,该方法处理Handler的选择和调用的结果,processDispatchResult方法调用processHandlerException
5.参考
https://docs.spring.io/spring/docs/current/javadoc-api/
https://github.com/spring-projects/spring-framework
文中难免有不足,欢迎指正
SpringMVC源码阅读:异常解析器的更多相关文章
- SpringMVC源码阅读:拦截器
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:视图解析器
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读系列汇总
1.前言 1.1 导入 SpringMVC是基于Servlet和Spring框架设计的Web框架,做JavaWeb的同学应该都知道 本文基于Spring4.3.7源码分析,(不要被图片欺骗了,手动滑稽 ...
- SpringMVC源码阅读:过滤器
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:核心分发器DispatcherServlet
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将介绍SpringMVC的核 ...
- SpringMVC源码阅读:Controller中参数解析
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:定位Controller
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码分析,弄清楚Spr ...
- SpringMVC源码阅读:Json,Xml自动转换
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:属性编辑器、数据绑定
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
随机推荐
- Android-ByteUtil工具类
Byte处理转换相关的工具类: public class ByteUtil { private ByteUtil(){} /** * 把byte[] 转成 Stirng * @param bytes ...
- 【TypeScript】TypeScript 学习 5——方法
在 JavaScript 中,有两种方式定义方法. 1.命名的方法 function add(x,y){ return x+y; } 2.匿名方法 var myAdd = function(x,y) ...
- Thread in depth 2:Asynchronization and Task
When we want to do a work asynchronously, creating a new thread is a good way. .NET provides two oth ...
- 【TFS 2017】使用浏览器上传文件(TFVC)或者编辑代码,错误提示TF14098,需要对文件有PendChange 权限
从TFS 2015开始,微软在TFS系统中增加了一个非常吸引开发人员的功能,"快速代码编辑器" (Quick Code Editor).使用这个功能,你可以在任何安装了浏览器的设备 ...
- linux系统编程之文件与IO(七):时间函数小结
从系统时钟获取时间方式 time函数介绍: 1.函数名称: localtime 2.函数名称: asctime 3.函数名称: ctime 4.函数名称: difftime 5.函数名称: gmtim ...
- CentOS系统安装遇到的一些问题
Vi操作:按ESC键 跳到命令模式,然后: :w 保存文件但不退出vi:w file 将修改另外保存到file中,不退出vi:w! 强制保存,不推出vi:wq 保存文件并退出vi:wq! 强制保存文件 ...
- C# null,string.Empty,"",DBNull 的区别
[null] null 在C# 中是一个关键字,表示不引用任何对象的空引用的文字值. null 是引用类型变量的默认值. 普通值类型不能为 null. null 在 VS 中定位不出来具体是怎么定义的 ...
- SHT20 IIC 寄存器概述
** 注:部分内容来自SHT20芯片手册翻译 ** 1.寄存器列表 名称 指令(bin) 寄存器内容(hex) 主机模式(Trigger T measurement hold master) 1110 ...
- sqlserver学习
清空数据表: delete from TableName 清除表中的所有的数据,保留表的结构 truncate table TableName 清除表中所有行,保留表结构 (重置ID) 删除表 Dr ...
- UWP开发---通过委托跨页面导航
-前言 做过.Net开发的都了解,当二级窗口操作主窗口的控件时通常用委托的方式.那么在UWP开发中,常常会遇到MainPage的二级Frame里面的内容去操作MainPage的导航跳转,具体看下图: ...