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的实现原理的更多相关文章

  1. 如何让 Spring Security 「少管闲事」

    记两种让 Spring Security「少管闲事」的方法. 遇到问题 一个应用对外提供 Rest 接口,接口的访问认证通过 Spring Security OAuth2 控制,token 形式为 J ...

  2. Mybatis源码解析(一) —— mybatis与Spring是如何整合的?

    Mybatis源码解析(一) -- mybatis与Spring是如何整合的?   从大学开始接触mybatis到现在差不多快3年了吧,最近寻思着使用3年了,我却还不清楚其内部实现细节,比如: 它是如 ...

  3. SpringMVC关于json、xml自动转换的原理研究[附带源码分析]

    目录 前言 现象 源码分析 实例讲解 关于配置 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.c ...

  4. SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转

    SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...

  5. spring IOC DI AOP MVC 事务, mybatis 源码解读

    demo https://gitee.com/easybao/aop.git spring DI运行时序 AbstractApplicationContext类的 refresh()方法 1: pre ...

  6. 微服务架构 | *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)

    目录 前言 1. Spring Cloud 什么时候加载配置文件 2. 准备 Environment 配置环境 2.1 配置 Environment 环境 SpringApplication.prep ...

  7. sobel算子原理及opencv源码实现

    sobel算子原理及opencv源码实现 简要描述 sobel算子主要用于获得数字图像的一阶梯度,常见的应用和物理意义是边缘检测. 原理 算子使用两个33的矩阵(图1)算子使用两个33的矩阵(图1)去 ...

  8. Android布局性能优化—从源码角度看ViewStub延迟加载技术

    在项目中,难免会遇到这种需求,在程序运行时需要动态根据条件来决定显示哪个View或某个布局,最通常的想法就是把需要动态显示的View都先写在布局中,然后把它们的可见性设为View.GONE,最后在代码 ...

  9. spring事务详解(三)源码详解

    系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

  10. jquery源码中noConflict(防止$和jQuery的命名冲突)的实现原理

    jquery源码中noConflict(防止$和jQuery的命名冲突)的实现原理 最近在看jquery源码分析的视频教学,希望将视频中学到的知识用博客记录下来,更希望对有同样对jquery源码有困惑 ...

随机推荐

  1. 基于4g智能路由器的充电桩远程监测管理应用

    随着我国电动车保有量的持续增加,充电桩的需求也水涨船高,成为城市发展必备的基础设施之一.相较加油站,充电桩分布更广泛,部署场景更多样,与场景的融合程度也更深,诸如各类停车与充电二合一的站点.因此,对于 ...

  2. mymath.so共享库

    共享库的使用(.so)文件   1.共享库的概念 2.创建共享库命令 # 1.将.c生成.o文件,(生成与位置无关的代码-fPIC)gcc -c add.c -o add.o -fPIC # 2.使用 ...

  3. ios装包

    一.下载爱思助手 二.找到本机设备 注:如果未弹出允许.拒绝调试选项可尝试换根数据线解决 三.将对应包体文件拖入本机设备

  4. Qt 5.15.2 QTextEdit无法设置新字体的处理方式

    首发于我的个人博客:xie-kang.com 博客内有更多文章,欢迎大家访问 原文地址 在使用QT 5.15.2 开发的过程中碰到了件怪事,下列代码无法给QTextEdit选中的文字设置字体: QTe ...

  5. unidbgrid按回车键切换到右侧CELL

    打开UniDBGrid的ClientEvents->ExtEvents属性,编辑Ext.grid.Panel的reconfig函数,输入如下代码就可以实现当UniDBGrid表格的ReadOnl ...

  6. 基本的dns命令

    打开cmd的方式 win+r 键 输入cmd       管理员方式运行    打开桌面  命令提示符 盘符切换 直接输入要切换的盘 查看当前目录下所有文件   dir 切换目录  cd  /d 跨盘 ...

  7. 给jui(dwz)的toolbar添加漂亮的图标

    前面两篇把菜单树和navTab的图标都换了.今天来添加toolbar的图标. 因为JUI(DWZ)自带的toolbar图标就三四个,根本不够用.于是只能是进行自定义添加 这是系统自带的图标,也就4个. ...

  8. [RoarCTF 2019]Easy Calc 1

    进入主页面是一个计算器,可以计算 右键源代码发现提示信息,javascript脚本,其中还有calc.php文件 注释说明了这里引入了waf 尝试访问calc.php 是一道命令执行,尝试输入phpi ...

  9. Linux 命令之 tar 操作符

    tar -c: 建立压缩档案 -x:解压 -t:查看内容 -r:向压缩归档文件末尾追加文件 -u:更新原压缩包中的文件 这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个 ...

  10. 【读书笔记】Nice Families Of GF

    目录 Nice Families Of GF rational rational algebraic D-finite总览 下定义 逻辑关系 例子 更多的例子和判别法 运算是否有性质? 运算是否有性质 ...