从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理
ExceptionHandler的作用
ExceptionHandler是Spring框架提供的一个注解,用于处理应用程序中的异常。当应用程序中发生异常时,ExceptionHandler将优先地拦截异常并处理它,然后将处理结果返回到前端。该注解可用于类级别和方法级别,以捕获不同级别的异常。
在Spring中使用ExceptionHandler非常简单,只需在需要捕获异常的方法上注解@ExceptionHandler,然后定义一个方法,该方法将接收异常并返回异常信息,并将该异常信息展示给前端用户。
ExceptionHandler的使用
说明:针对可能出问题的Controller,新增注解方法@ExceptionHandler,下面是一个基本的ExceptionHandler示例:
java
@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 .
java
HandlerExecutionChain mappedHandler = null;Exception dispatchException = null;ModelAndView mv = null;try{//根据请求查找handlerMapping找到controllermappedHandler=getHandler(request); //找到处理器适配器HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); if(!mappedHandler.applyPreHandle(request,response)){ //拦截器preHandlereturn ;} //调用处理器适配器执行@RequestMapping方法mv=ha.handle(request,response); //拦截器postHandlemappedHandler.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接口
java
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {@Overridepublic 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的简单例子.
java
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {@Overridepublic 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等多种形式。
java
@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实现类,多少有点性能损耗.
- ExceptionHandler的作用
- ExceptionHandler的使用
- ExceptionHandler的注意事项
- 源码分析介绍
- 原理说明-doDispatch
- 原理说明-processDispatchResult
- 原理说明-processHandlerException
- 原理说明-AbstractHandlerExceptionResolver
- 原理说明-AbstractHandlerMethodExceptionResolver
- 父类AbstractHandlerExceptionResolver的shouldApplyTo方法.
- doResolveException
- 全局级别异常处理器实现HandlerExceptionResolver接口
- 全局级别异常处理器@ControllerAdvice+@ExceptionHandler使用方法
- 比较说明
- 调用优先级
- 三种方式都支持多种返回类型
- 缓存利用
相关文章:

从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理
ExceptionHandler的作用 ExceptionHandler是Spring框架提供的一个注解,用于处理应用程序中的异常。当应用程序中发生异常时,ExceptionHandler将优先地拦截异常并处理它,然后将处理结果返回到前端。该注解可用于类级别和方法级别,…...
04mysql查询语句之查询与分页02
1. 所有有门派的人员信息 ( A、B两表共有) INSERT INTO t_dept(deptName,address) VALUES(华山,华山); INSERT INTO t_dept(deptName,address) VALUES(丐帮,洛阳); INSERT INTO t_dept(deptName,address) VALUES(峨眉,峨眉山); INSERT INTO t_dept(deptN…...

原型模式——对象的克隆
1、简介 1.1、概述 可以通过一个原型对象克隆出多个一模一样的对象,该模式被称为原型模式。 在使用原型模式时,需要首先创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。 1.2、定义 原型模式(Prototype Patt…...
[SQL挖掘机] - 多表连接
介绍: 在 SQL 中,多表连接是指将多个表根据某些条件进行联接,以获取相关联的数据。这允许我们跨多个表进行查询,并且根据表之间的关系获取所需的结果。 作用: 当在多个表中存储相关数据时,使用多表连接可以将这些表组合起来以获…...
Day 14 C++ 对象的初始化和清理
目录 为什么要进行对象的初始化和清理 构造函数和析构函数 构造函数(Constructor) 构造函数语法 调用时机 构造函数的调用方式 括号法 显式法 隐式转换法 构造函数分类 分类方式 按参数分为有参构造和无参构造 按类型分为普通构造和拷贝构造…...

Delphi7最佳登录窗体设计
Delphi7我们这里用登录窗体来做演示。输入正确用户名和密码后,登录窗体释放,显示主窗体。 方法/步骤 1.打开Delphi7集成开发环境,在默认工程的Form1窗体放置一个Label1控件,拖动控件边界调整大小,并将Object Inspect…...

动脑学院Jetpack Compose学习笔记
最近b站学习了一下Compose相关内容,整理了相关笔记,仅供大家参考。 资源链接如下,象征性收取1个积分 https://download.csdn.net/download/juliantem/88125198...
Qt中线程的使用
Qt中线程的使用 在qt中线程的使用有两种方式,第一种就是创建一个类继承QObject类,之后使用moveToThread函数将线程添加到类中。另一种就是创建一个类继承QThread类,在类中实现run函数。 第一种方式: 1、首先创建一个自定义的类…...

基于YOLOv8开发构建蝴蝶目标检测识别系统
在前面的一篇博文中已经很详细地描述了如何基于YOLOv8开发构建自己的个性化目标检测模型,感兴趣的话可以看下: 《基于YOLOv8开发构建目标检测模型超详细教程【以焊缝质量检测数据场景为例】》 本文的主要目的就是基于YOLOv8来开发构建细粒度的蝴蝶目标…...

【已解决】电脑连上网线但无法上网
文章目录 案例情况解决方案必要的解决方法简要概括详细步骤1、打开控制面板2、打开更改适配器设置3、 找Internet协议版本44、修改配置 可能有用的解决方法 问题解决原理Internet 协议版本 4(TCP/IPv4)确保IP地址和DNS服务器设置为自动获取 案例情况 网…...

Linux 学习记录57(ARM篇)
Linux 学习记录57(ARM篇) 本文目录 Linux 学习记录57(ARM篇)一、外部中断1. 概念2. 流程图框 二、相关寄存器1. GIC CPU Interface (GICC)2. GIC distributor (GICD)3. EXTI registers 三、EXTI 寄存器1. 概述2. 内部框图3. 寄存器功能描述4. EXTI选择框图5. EXTI_EXTICR1 &…...

Doris注意事项,Doris部署在阿里云,写不进去数据
1.Doris官网 Doris官网https://doris.apache.org/ 2.根本原因 本地idea访问FE,FE会返回BE的地址,但是在服务器上通过ip addr查看,发现只有局域网IP,所以FE返回了局域网的IP,导致idea连接不上BE 3.解决办法 重写Ba…...

502 Bad GateWay报错的解决方法
什么是502 bad gateway 报错 简单来说 502 是报错类型代码 bad gateway 错误的网关。是Web服务器作为网关或代理服务器时收到无效的响应。 用我们的口语说就是运行网站的服务器暂时挂了(不响应)。 产生错误的原因 1.连接超时 我们向服务器发送请求 由于服务器当前链接太多&am…...

openpnp - ReferenceStripFeeder 改版零件
文章目录 openpnp - ReferenceStripFeeder 改版零件概述笔记整体效果散料飞达主体磁铁仓盖板飞达编带中间压条飞达编带两边压条装配体用的8mm编带模型END openpnp - ReferenceStripFeeder 改版零件 概述 官方推荐了ReferenceStripFeeder的模型smd_strip_feeders_mod_tray.zip…...
VoxPoser:使用大语言模型(GPT-4)来对机器人操作的可组合三维值图【论文解读】
这是最近斯坦福的李飞飞团队的一篇论文:VoxPoser: Composable 3D Value Maps for Robotic Manipulation with Language Models 主要是通过大语言模型LLM和视觉语言模型VLM结合,来对机器人做各种日常操作,我们可以先来看下实际效果:大语言模型…...

RISC-V公测平台发布 · 第一个WEB Server “Hello RISC-V world!”
RISC-V公测平台Web Server地址:http://175.8.161.253:8081 一、前言 Web Server是互联网应用的基础设施,无论是用户访问网站,还是后端服务提供商和开发者构建各种应用程序,Web Server都在其中扮演着至关重要的角色。 显而易见…...
Linux 发行版 CentOS 于 Ubuntu 软件的安装、卸载、查找
CentOS于Ubuntu 内核都是Linux,是一样的。 CentOS 软件格式 .rpm sudo yum [-y] [ install | remove | search ] 软件名称 install 安装 remove 移除 search 搜索 Ubuntu 软件格式 .deb sudo apt [-y] [ install | remove | search ] 软件名称 install 安装 remove…...

cmd相关操作命令
1.根据端口号查询对应进程的PID netstat -ano | findstr 端口号 例如:netstat -ano | findstr 9080;该端口所属进程的PID为6684 2.根据PID查询对应进程 tasklist | findstr PID 例如:tasklist | findstr 6684;该PID所属进程名为…...

使用EM算法完成聚类任务
EM算法(Expectation-Maximization Algorithm)是一种基于迭代优化的聚类算法,用于在无监督的情况下将数据集分成几个不同的组或簇。EM算法是一种迭代算法,包含两个主要步骤:期望步骤(E-step)和最…...

❤️创意网页:创意视觉效果粒子循环的网页动画
✨博主:命运之光 🌸专栏:Python星辰秘典 🐳专栏:web开发(简单好用又好看) ❤️专栏:Java经典程序设计 ☀️博主的其他文章:点击进入博主的主页 前言:欢迎踏入…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...