【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理
ExceptionHandler的作用
ExceptionHandler是Spring框架提供的一个注解,用于处理应用程序中的异常。当应用程序中发生异常时,ExceptionHandler将优先地拦截异常并处理它,然后将处理结果返回到前端。该注解可用于类级别和方法级别,以捕获不同级别的异常。
在Spring中使用ExceptionHandler非常简单,只需在需要捕获异常的方法上注解@ExceptionHandler,然后定义一个方法,该方法将接收异常并返回异常信息,并将该异常信息展示给前端用户。
ExceptionHandler的使用
说明:针对可能出问题的Controller,新增注解方法@ExceptionHandler,下面是一个基本的ExceptionHandler示例:
@RestController
public class ExceptionController {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An error occurred: " + ex.getMessage());
}
@RequestMapping("/test")
public String test() throws Exception {
throw new Exception("Test exception!");
}
}
在上面的示例中,我们定义了一个叫做ExceptionController的类,该类是一个@RestController注解的控制器,它包括一个可以产生异常的请求处理程序,一个用于捕获和处理异常的@ExceptionHandler方法。
@RequestMapping注解配置了一个名为“/test”的API,该API将抛出一个异常,该异常将由我们上面的ExceptionHandler进行处理。当请求“/test”时,Controller方法将引发异常并触发@ExceptionHandler方法。
在上面的@ExceptionHandler方法中,我们通过ResponseEntity将异常信息提供给客户端,HTTP状态码设置为500。这使客户端了解已发生错误,并能够在日志中记录异常信息以便日后调试。
总之,使用ExceptionHandler能够更好的掌控应用的异常信息,使得应用在发生异常的时候更加可控,并且更加容易进行调试。
ExceptionHandler的注意事项
Controller类下多个@ExceptionHandler上的异常类型不能出现一样的,否则运行时抛异常。
@ExceptionHandler下方法返回值类型支持多种,常见的ModelAndView,@ResponseBody注解标注,ResponseEntity等类型都OK.
源码分析介绍
原理说明-doDispatch
代码片段位于:org.springframework.web.servlet.DispatcherServlet#doDispatch
执行@RequestMapping方法抛出异常后,Spring框架 try-catch的方法捕获异常, 正常逻辑发不发生异常都会走processDispatchResult流程 ,区别在于异常的参数是否为null .
HandlerExecutionChain mappedHandler = null;
Exception dispatchException = null;
ModelAndView mv = null;
try{
//根据请求查找handlerMapping找到controller
mappedHandler=getHandler(request);
//找到处理器适配器HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
if(!mappedHandler.applyPreHandle(request,response)){
//拦截器preHandle
return ;
}
//调用处理器适配器执行@RequestMapping方法
mv=ha.handle(request,response);
//拦截器postHandle
mappedHandler.applyPostHandle(request,response,mv);
}catch(Exception ex){
dispatchException=ex;
}
//将异常信息传入了
processDispatchResult(request,response,mappedHandler,mv,dispatchException)
原理说明-processDispatchResult
代码片段位于:org.springframework.web.servlet.DispatcherServlet#processDispatchResult
如果 @RequestMapping 方法抛出异常,拦截器的postHandle方法不执行,进入processDispatchResult,判断入参dispatchException,不为null , 代表发生异常,调用processHandlerException处理。
原理说明-processHandlerException
代码片段位于:org.springframework.web.servlet.DispatcherServlet#processHandlerException
this当前对象指dispatchServlet,handlerExceptionResolvers可以看到三个HandlerExceptionResolver,这三个是Spring框架帮我们注册的,遍历有序集合handlerExceptionResolvers,调用接口的resolveException方法。
注册的第一个HandlerExceptionResolver.ExceptionHandlerExceptionResolver, 继承关系如下面所示。
原理说明-AbstractHandlerExceptionResolver
代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#resolveException
这里AbstractHandlerExceptionResolver的shouldApplyTo都返回true, logException用来记录日志、prepareResponse方法,用来设置response的Cache-Control。
异常处理方法就位于doResolveException
注意:AbstractHandlerExceptionResolver和AbstractHandlerMethodExceptionResolver名字看起来非常相似,但是作用不同,一个是面向整个类的,一个是面向方法级别的。
原理说明-AbstractHandlerMethodExceptionResolver
代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#shouldApplyTo
接口方法实现AbstractHandlerExceptionResolver的resolveException,先判断shouldApplyTo,AbstractHandlerExceptionResolver 和子类AbstractHandlerMethodExceptionResolver都实现了shouldApplyTo方法,子类的shouldApplyTo都调用父类AbstractHandlerExceptionResolver的shouldApplyTo.
父类AbstractHandlerExceptionResolver的shouldApplyTo方法.
代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#shouldApplyTo
Spring初始化的时候并没有额外配置 , 所以mappedHandlers和mappedHandlerClasses都为null, 可以在这块扩展进行筛选 ,AbstractHandlerExceptionResolver提供了setMappedHandlerClasses 、setMappedHandlers用于扩展。
doResolveException
代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#doResolveException
Spring请求方法执行一样的处理方式,设置argumentResolvers、returnValueHandlers,之后进行调用异常处理方法。
获取@ExceptionHandler
@ExceptionHandler的方法入参支持:Exception ;SessionAttribute 、 RequestAttribute注解、 HttpServletRequest 、HttpServletResponse、HttpSession。
@ExceptionHandler方法返回值常见的可以是: ModelAndView 、@ResponseBody注解、ResponseEntity。
getExceptionHandlerMethod方法
getExceptionHandlerMethod说明: 获取对应的@ExceptionHandler方法,封装成ServletInvocableHandlerMethod返回。
exceptionHandlerCache是针对Controller层面的@ExceptionHandler的处理方式,而exceptionHandlerAdviceCache是针对@ControllerAdvice的处理方式. 这两个属性都位于ExceptionHandlerExceptionResolver中。
ExceptionHandlerMethodResolver,缓存A之前没存储过Controller的class ,所以新建一个ExceptionHandlerMethodResolver 加入缓存中,ExceptionHandlerMethodResolver 的初始化工作一定做了某些工作。
resolveMethod方法
根据异常对象让 ExceptionHandlerMethodResolver 解析得到 method , 匹配到异常处理方法就直接封装成对象 ServletInvocableHandlerMethod ; 就不会再去走@ControllerAdvice里的异常处理器了,这里说明了。
resolveMethodByExceptionType根据当前抛出异常寻找 匹配的方法,并且做了缓存,以后遇到同样的异常可以直接走缓存取出
resolveMethodByExceptionType方法,尝试从缓存A:exceptionLookupCache中根据异常class类型获取Method ,初始时候肯定缓存为空 ,就去遍历ExceptionHandlerMethodResolver的mappedMethods(上面提及了key为异常类型,value为method,exceptionType为当前@RequestMapping方法抛出的异常,判断当前异常类型是不是@ExceptionHandler中value声明的子类或本身,满足条件就代表匹配上了;
可能存在多个匹配的方法,使用ExceptionDepthComparator排序,排序规则是按照继承顺序来(继承关系越靠近数值越小,当前类最小为0,顶级父类Throwable为int最大值),排序之后选取继承关系最靠近的那个,并且ExceptionHandlerMethodResolver的exceptionLookupCache中,key为当前抛出的异常,value为解析出来的匹配method.
全局级别异常处理器实现HandlerExceptionResolver接口
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelMap mmp=new ModelMap();
mmp.addAttribute("ex",ex.getMessage());
return new ModelAndView("error",mmp);
}
}
使用方式: 只需要将该Bean加入到Spring容器,可以通过Xml配置,也可以通过注解方式加入容器;
方法返回值不为null才有意义,如果方法返回值为null,可能异常就没有被捕获.缺点分析:比如这种方式全局异常处理返回JSP、velocity等视图比较方便,返回json或者xml等格式的响应就需要自己实现了.如下是我实现的发生全局异常返回JSON的简单例子.
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("发生全局异常!");
ModelMap mmp=new ModelMap();
mmp.addAttribute("ex",ex.getMessage());
response.addHeader("Content-Type","application/json;charset=UTF-8");
try {
new ObjectMapper().writeValue(response.getWriter(),ex.getMessage());
response.getWriter().flush();
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView();
}
}
全局级别异常处理器@ControllerAdvice+@ExceptionHandler使用方法
用法说明:这种情况下 @ExceptionHandler与第一种方式用法相同,返回值支持ModelAndView,@ResponseBody等多种形式。
@ControllerAdvice
public class GlobalController {
@ExceptionHandler(RuntimeException.class)
public ModelAndView fix1(Exception e){
System.out.println("全局的异常处理器");
ModelMap mmp=new ModelMap();
mmp.addAttribute("ex",e);
return new ModelAndView("error",mmp);
}
}
- 方式一:提到ExceptionHandlerExceptionResolver不仅维护@Controller级别的@ExceptionHandler,同时还维护的@ControllerAdvice级别的@ExceptionHandler代码片段位于:
isApplicableToBeanType方法是用来做条件判断的,@ControllerAdvice注解有很多属性用来设置条件,
basePackageClasses、assignableTypes、annotations等,比如我限定了annotations为注解X, 那标注了@X 的ControllerA就可以走这个异常处理器,ControllerB就不能走这个异常处理器。
现在问题的关键就只剩下了exceptionHandlerAdviceCache是什么时候扫描@ControllerAdvice的,下面的逻辑和@ExceptionHandler的逻辑一样了,exceptionHandlerAdviceCache初始化逻辑:
代码片段位于:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#afterPropertiesSet,afterPropertiesSet是Spring bean创建过程中一个重要环节。
代码片段位于:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache
ControllerAdviceBean.findAnnotatedBeans方法查找了SpringMvc父子容器中标注 @ControllerAdvice 的bean, new ExceptionHandlerMethodResolver初始化时候解析了当前的@ControllerAdvice的bean的@ExceptionHandler,加入到ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,key为ControllerAdviceBean,value为ExceptionHandlerMethodResolver . 到这里exceptionHandlerAdviceCache就初始化完毕。
Spring父子容器中所有@ControllerAdivce的bean的方法
代码片段位于:org.springframework.web.method.ControllerAdviceBean#findAnnotatedBeans
遍历了SpringMVC父子容器中所有的bean,标注ControllerAdvice注解的bean加入集合返回。
比较说明
@Controller+@ExceptionHandler、HandlerExceptionResolver接口形式、@ControllerAdvice+@ExceptionHandler优缺点说明:
调用优先级
- @Controller+@ExceptionHandler优先级最高
- @ControllerAdvice+@ExceptionHandler 略低
- HandlerExceptionResolver最低。
三种方式并存的情况 优先级越高的越先选择,而且被一个捕获处理了就不去执行其他的。
三种方式都支持多种返回类型
@Controller+@ExceptionHandler、@ControllerAdvice+@ExceptionHandler可以使用Spring支持的@ResponseBody、ResponseEntity。
HandlerExceptionResolver方法声明返回值类型只能是 ModelAndView,如果需要返回JSON、xml等需要自己实现.。
缓存利用
@Controller+@ExceptionHandler的缓存信息在ExceptionHandlerExceptionResolver的exceptionHandlerCache,@ControllerAdvice+@ExceptionHandler的缓存信息在ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,
HandlerExceptionResolver接口是不做缓存的,在异常报错的情况下才会走自己的HandlerExceptionResolver实现类,多少有点性能损耗.
【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理的更多相关文章
- 如何让 Spring Security 「少管闲事」
记两种让 Spring Security「少管闲事」的方法. 遇到问题 一个应用对外提供 Rest 接口,接口的访问认证通过 Spring Security OAuth2 控制,token 形式为 J ...
- Mybatis源码解析(一) —— mybatis与Spring是如何整合的?
Mybatis源码解析(一) -- mybatis与Spring是如何整合的? 从大学开始接触mybatis到现在差不多快3年了吧,最近寻思着使用3年了,我却还不清楚其内部实现细节,比如: 它是如 ...
- SpringMVC关于json、xml自动转换的原理研究[附带源码分析]
目录 前言 现象 源码分析 实例讲解 关于配置 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.c ...
- SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转
SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...
- spring IOC DI AOP MVC 事务, mybatis 源码解读
demo https://gitee.com/easybao/aop.git spring DI运行时序 AbstractApplicationContext类的 refresh()方法 1: pre ...
- 微服务架构 | *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)
目录 前言 1. Spring Cloud 什么时候加载配置文件 2. 准备 Environment 配置环境 2.1 配置 Environment 环境 SpringApplication.prep ...
- sobel算子原理及opencv源码实现
sobel算子原理及opencv源码实现 简要描述 sobel算子主要用于获得数字图像的一阶梯度,常见的应用和物理意义是边缘检测. 原理 算子使用两个33的矩阵(图1)算子使用两个33的矩阵(图1)去 ...
- Android布局性能优化—从源码角度看ViewStub延迟加载技术
在项目中,难免会遇到这种需求,在程序运行时需要动态根据条件来决定显示哪个View或某个布局,最通常的想法就是把需要动态显示的View都先写在布局中,然后把它们的可见性设为View.GONE,最后在代码 ...
- spring事务详解(三)源码详解
系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...
- jquery源码中noConflict(防止$和jQuery的命名冲突)的实现原理
jquery源码中noConflict(防止$和jQuery的命名冲突)的实现原理 最近在看jquery源码分析的视频教学,希望将视频中学到的知识用博客记录下来,更希望对有同样对jquery源码有困惑 ...
随机推荐
- 基于4g智能路由器的充电桩远程监测管理应用
随着我国电动车保有量的持续增加,充电桩的需求也水涨船高,成为城市发展必备的基础设施之一.相较加油站,充电桩分布更广泛,部署场景更多样,与场景的融合程度也更深,诸如各类停车与充电二合一的站点.因此,对于 ...
- mymath.so共享库
共享库的使用(.so)文件 1.共享库的概念 2.创建共享库命令 # 1.将.c生成.o文件,(生成与位置无关的代码-fPIC)gcc -c add.c -o add.o -fPIC # 2.使用 ...
- ios装包
一.下载爱思助手 二.找到本机设备 注:如果未弹出允许.拒绝调试选项可尝试换根数据线解决 三.将对应包体文件拖入本机设备
- Qt 5.15.2 QTextEdit无法设置新字体的处理方式
首发于我的个人博客:xie-kang.com 博客内有更多文章,欢迎大家访问 原文地址 在使用QT 5.15.2 开发的过程中碰到了件怪事,下列代码无法给QTextEdit选中的文字设置字体: QTe ...
- unidbgrid按回车键切换到右侧CELL
打开UniDBGrid的ClientEvents->ExtEvents属性,编辑Ext.grid.Panel的reconfig函数,输入如下代码就可以实现当UniDBGrid表格的ReadOnl ...
- 基本的dns命令
打开cmd的方式 win+r 键 输入cmd 管理员方式运行 打开桌面 命令提示符 盘符切换 直接输入要切换的盘 查看当前目录下所有文件 dir 切换目录 cd /d 跨盘 ...
- 给jui(dwz)的toolbar添加漂亮的图标
前面两篇把菜单树和navTab的图标都换了.今天来添加toolbar的图标. 因为JUI(DWZ)自带的toolbar图标就三四个,根本不够用.于是只能是进行自定义添加 这是系统自带的图标,也就4个. ...
- [RoarCTF 2019]Easy Calc 1
进入主页面是一个计算器,可以计算 右键源代码发现提示信息,javascript脚本,其中还有calc.php文件 注释说明了这里引入了waf 尝试访问calc.php 是一道命令执行,尝试输入phpi ...
- Linux 命令之 tar 操作符
tar -c: 建立压缩档案 -x:解压 -t:查看内容 -r:向压缩归档文件末尾追加文件 -u:更新原压缩包中的文件 这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个 ...
- 【读书笔记】Nice Families Of GF
目录 Nice Families Of GF rational rational algebraic D-finite总览 下定义 逻辑关系 例子 更多的例子和判别法 运算是否有性质? 运算是否有性质 ...