SpringBoot全局异常处理源码
SpringBoot全局异常处理源码
- 一、SpringMVC执行流程
- 二、SpringBoot源码跟踪
- 三、自定义优雅的全局异常处理脚手架starter
- 自定义异常
- 国际化引入
- 封装基础异常
- 封装基础异常扫描器,并注册到ExceptionHandler中
- 项目分享以及改进点
一、SpringMVC执行流程
今天这里叙述的全局异常处理是SpringBoot在Servlet场景下的处理机制,重点是
Servlet
模式,当然WEBFLUX
今天不做过多描述,SpringBoot2.2.x以后引入的一种响应式web开发,在SpringBoot启动类中可以看到:
SpringApplication.java=> new SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) => WebApplicationType.deduceFromClasspath();
deduceFromClasspath
方法:
既然是SringBoot的webServlet场景,自然不可以放过的就是DispatchServlet
一整个执行流程,那就从面试书籍中cp一张
当然小编也有历史文档可以参考下:SpringMVC执行流程
今天的异常处理,用草图画了下,就是红框框这里:
这里SpringBoot究竟如何设计了异常处理呢,走进源码,探索真相!
二、SpringBoot源码跟踪
说到DispatchServlet
的请求处理,那就直接找到核心方法:doDispatch(HttpServletRequest request, HttpServletResponse response)
;点进源码,不难发现寻找Handler和执行Handler这整整一大块,用了复合try–catch进行包裹:
try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());......mv = ha.handle(processedRequest, response, mappedHandler.getHandler());......applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);}processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
在try的结束处,我们可以看到,小异常到大异常,源码中并没有打印堆栈,而是封装成dispatchException
,最后交给processDispatchResult
方法去处理请求分发的结果
而processDispatchResult
方法内部,则是对异常进行了解析,也叫resolveException
:
if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}
再次进入非视图异常的处理方法processHandlerException中,我们看到了多个异常处理器去循环处理异常,直到循环结束,如果返回值不为NULL,说明该异常能够被解析并且处理完毕返回ModelAndView
if (this.handlerExceptionResolvers != null) {for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {exMv = resolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}}
DispatchServlet
类初始化时从容器中获取handlerExceptionResolvers
,该类的接口表示Spring容器中处理异常的处理器类,根据debug可以看到,Spring容器中含有两个解析器类,一个是默认的兜底的异常解析器类,另一个是HandlerExceptionResolverComposite,内部维护着spring容器的异常解析器列表
那么HandlerExceptionResolverComposite
处理器类是从哪里来的,接着我们跳转到WebMvcConfigurationSupport
类,观察其诞生之地
@Beanpublic HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();configureHandlerExceptionResolvers(exceptionResolvers);if (exceptionResolvers.isEmpty()) {addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);}extendHandlerExceptionResolvers(exceptionResolvers);HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();composite.setOrder(0);composite.setExceptionResolvers(exceptionResolvers);return composite;}
这里有两个地方需要关注,首先是addDefaultHandlerExceptionResolvers
,就是spring会默认添加三个异常解析器,一个是ExceptionHandlerExceptionResolver
,这个处理的是程序中注解了@ExceptionHandler的,第二个DefaultHandlerExceptionResolver
,这个是处理一些通常的异常,具体可查看官方文档。第三个是较少用的ResponseStatusExceptionResolver
另一个要关注的是extendHandlerExceptionResolvers
方法,这个是留给子类重写,扩展使用的。
此时我们大概知道HandlerExceptionResolverComposite
类的resolveException方法可以解析异常,那么我们打个断点,放行程序到此处,再观察:
那么一切就变的似乎很合理了,HandlerExceptionResolverComposite内部维护着异常解析器列表,循环去解析,解析成功就返回,并且还看到了列表清单的第一个解析器就是ExceptionHandlerExceptionResolver
,于是到ExceptionHandlerExceptionResolver
类中打上断点观察
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);if (exceptionHandlerMethod == null) {return null;}if (this.argumentResolvers != null) {exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}ServletWebRequest webRequest = new ServletWebRequest(request, response);ModelAndViewContainer mavContainer = new ModelAndViewContainer();ArrayList<Throwable> exceptions = new ArrayList<>();try {if (logger.isDebugEnabled()) {logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);}// Expose causes as provided arguments as wellThrowable exToExpose = exception;while (exToExpose != null) {exceptions.add(exToExpose);Throwable cause = exToExpose.getCause();exToExpose = (cause != exToExpose ? cause : null);}Object[] arguments = new Object[exceptions.size() + 1];exceptions.toArray(arguments); // efficient arraycopy call in ArrayListarguments[arguments.length - 1] = handlerMethod;exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);}catch (Throwable invocationEx) {// Any other than the original exception (or a cause) is unintended here,// probably an accident (e.g. failed assertion or the like).if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);}// Continue with default processing of the original exception...return null;}if (mavContainer.isRequestHandled()) {return new ModelAndView();}
观察这一句
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
顾名思义,设置了返回值的处理器
那我们看看ExceptionHandlerExceptionResolver
初始化经历了些啥,首先实现了InitializingBean,那么就直接先看afterProperties方法(Bean生命周期执行的钩子函数)
initExceptionHandlerAdviceCache
方法:
言简意赅就是获取所有标有@ControllerAdvice
注解的类,并封装成ControllerAdviceBean
,随后又去根据这些类创建ExceptionHandlerMethodResolver类,点击进去ExceptionHandlerMethodResolver的构造函数
addMapping方法:
现在真相几乎大告于天下,这里先引入SpringBoot的异常处理机制@ControllerAdvice+@ExceptionHandler;用起来很简单,在处理类上添加ControllerAdvice注解、在类中方法上添加ExceptionHandler注解并标注捕获的类,那么SpringBoot整个webServlet执行过程中产生的异常都会被这个异常捕获并且返回对应方法的返回值;
所以,我们后续处理无非就是从mapCache中寻找异常对应的方法,因为addMapping方法已经将异常全部封装成exception-Method的map集合形式;再一层层返回给dispatchServlet。
三、自定义优雅的全局异常处理脚手架starter
上述的源码跟踪下来,@ControllerAdvice+@ExceptionHandler模式是不是有一些鸡肋?完全可以定义一个全局的ExceptionHandler类,内部封装自定义异常,再配合EnableAutoConfiguration,达到脚手架starter封装的效果;这里我大概叙述一下思路
自定义异常
定义一个类实现RuntimeException类,同时考虑到国际化的问题,这里加入了枚举类BaseError
,并且框架常见异常和业务异常进行分类注册
国际化引入
枚举异常基类默认实现I18n接口,并返回resources文件中定义异常文件的文件名
[外链图片转存失败,源站可能有防盗在这里插入!链机制,建描述](https://img-blog议将存csdnimg下cn/d84acd6585a248f29c1d52d1084bbfdf.png
封装基础异常
既然作为脚手架使用,那么系统中常见的异常我们可以封装一下了,我们封装到国际化的Bundle中
然后定义枚举专门去getClass
封装基础异常扫描器,并注册到ExceptionHandler中
这里用了reflections.getSubTypesOf方法返回类路径基础异常SysBaseEnum类及其子类实现,封装成集合遍历并抽取其中的枚举类,最终枚举集合将注册到exceptionHandler方法中进行捕获
## 自定义全局异常处理注解以及异常解析器
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@ResponseBody
public @interface SangExceptionAdvice {}
这里模仿ExceptionHandlerResolver去继承它:
public class SangExceptionHandlerExceptionResolver extends ExceptionHandlerExceptionResolver implements ApplicationContextAware, InitializingBean
核心代码:封装自定义注解@SangExceptionAdvice
成为SangExceptionAdviceBean
,并重写doResolveHandlerMethodException
方法
项目分享以及改进点
SangExceptionAdviceBean
类封装时可以根据设定加入Predicate断言器
,配合ConfigurationProperties实现路径匹配捕获异常、全路径异常捕获等等功能改进点:reflections.getSubTypesOf方法反射获取异常基类时有些许不合理,后期慢慢调整,也欢迎大家指教
代码半成品框架开源地址:gitee地址,欢迎大家fork!多沟通,一起学习,一起进步!
如果喜欢本篇文章,点个赞吧!
相关文章:
SpringBoot全局异常处理源码
SpringBoot全局异常处理源码 一、SpringMVC执行流程二、SpringBoot源码跟踪三、自定义优雅的全局异常处理脚手架starter自定义异常国际化引入封装基础异常封装基础异常扫描器,并注册到ExceptionHandler中项目分享以及改进点 一、SpringMVC执行流程 今天这里叙述的全…...
设计模式——7. 装饰者模式
1. 说明 装饰者模式(Decorator Pattern)是一种结构型设计模式,它允许你在不改变对象接口的前提下,动态地将新行为附加到对象上。这种模式是通过创建一个包装(或装饰)对象,将要被装饰的对象包裹起来,从而实现对原有对象功能的增强和扩展。 装饰者模式的主要特点包括:…...
安卓玩机-----反编译apk 修改apk 去广告 去弹窗等操作中的一些常识
安卓机型app的编译与反编译 apk文件的简单说明与解析 -安卓修改apk apk的组成和编译 一 电脑端几种反编译apk工具操作步骤解析 前面几个博文有说明关于反编译apk和apk架构等有些常识.今天对以上做个补充。初学者记住一点。对于一个apk文件使用压缩软件7zip打开可以查看到文件…...
Hoeffing不等式
在李航老师的统计学习方法(第一版中) H o e f f i n g 不等式 Hoeffing不等式 Hoeffing不等式是这样子给出的 设 X 1 , X 2 , . . . , X N X_1,X_2,...,X_N X1,X2,...,XN是独立随机变量,且 X i ∈ [ a i , b i ] , i 1 , 2 , . . . ,…...
ffmpeg解复用指定pid转推udp
命令 ffmpeg -re -i udp://224.2.2.2:4003?fifo_size1024000 -map #5001 -acodec copy -flush_packets 1 -f mpegts udp://192.168.2.62:5161 ffmpeg -re -i udp://224.2.2.2:4003?fifo_size1024000 -map #5001 -acodec copy -flush_packets 1 -f mpegts udp://192.16…...
Vue组件通信方式
1.props通信 1.1在 Vue 2 中使用 props 通信 注意:props传递的数据是只读的,子组件修改,不会影响父组件 1.1.1.定义 props 在子组件中使用 props 选项来定义要接收的属性 // 子组件 <script> export default {props: {message: String} } </script>1.1.2.传递…...
redis-设置从节点
节点结构 节点配置文件 主节点 不变 6380节点 port 6380 slaveof 127.0.0.1 63796381节点 port 6381 slaveof 127.0.0.1 6380启动 指定配置文件的方式启动 D:\jiqun\redis\Redis-6380>redis-server.exe redis.windows.conf启动时,会触发同步数据命令 主节点…...
k8s-实战——基于nfs实现动态存储
部署nfs服务 基于Centos7.9版本创建动态存储注意k8s版本应低于v1.24版本高k8s版本ServiceAccount需要手动创建secrets并关联相关sa部署流程 创建NFS共享服务、采用单独的节点用于nfs服务独占安装nfs-utils和rpcbindnfs客户端和服务端都安装nfs-utils包通过部署化脚本安装k8s集群…...
【广州华锐互动】鱼类授精繁殖VR虚拟仿真实训系统
随着科技的不断发展,虚拟现实技术在各个领域的应用越来越广泛。在养殖业中,VR技术可以帮助养殖户进行家鱼授精实操演练,提高养殖效率和繁殖成功率。本文将介绍利用VR开展家鱼授精实操演练的方法和应用。 首先,我们需要了解家鱼授精…...
RocketMQ Promethus Exporter
介绍 Rocketmq-exporter 是用于监控 RocketMQ broker 端和客户端所有相关指标的系统,通过 mqAdmin 从 broker 端获取指标值后封装成 87 个 cache。 警告 过去版本曾是 87 个 concurrentHashMap,由于 Map 不会删除过期指标,所以一旦有 la…...
Kafka收发消息核心参数详解
文章目录 1、从基础的客户端说起1.1、消息发送者主流程1.2、消息消费者主流程 2、从客户端属性来梳理客户端工作机制2.1、消费者分组消费机制 1、从基础的客户端说起 Kafka提供了非常简单的客户端API。只需要引入一个Maven依赖即可: <dependency><groupId…...
Springboot中Aop的使用
Springboot中使用拦截器、过滤器、监听器-CSDN博客 相比较于拦截器,Spring 的aop则功能更强大,封装的更细致,需要单独引用 jar包。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-b…...
创建vue3项目、链式调用、setup函数、ref函数、reactive函数、计算和监听属性、vue3的生命周期、torefs的使用、vue3的setup写法
1 创建vue3项目 # 两种方式- vue-cli:vue脚手架---》创建vue项目---》构建vue项目--》工具链跟之前一样- vite :https://cn.vitejs.dev/-npm create vuelatest // 或者-npm create vitelatest一路选择即可# 运行vue3项目-vue-cli跟之前一样-vite 创建的…...
搭建好自己的PyPi服务器后怎么使用
当您成功搭建好自己的 PyPI 服务器后,您可以使用以下步骤来发布和使用您的包: 打包您的代码: 首先,将您的 Python 项目打包成一个发布包。确保您已经在项目根目录下创建了 setup.py 文件,并按照正确的格式填写了项目信…...
Vue3 中使用provide和reject
1、provide 和reject 可以实现一条事件线上的 父传子,父传孙等;相比较 props emits 仅限与父子传参更方便,相较于pinia书写更简单,但是需要注意使用响应式,如果是非响应式的会导致页面更新不及时 父组件 <templat…...
大数据flink篇之一-基础知识
一、起源 2010至2014年间,由柏林工业大学、柏林洪堡大学和哈索普拉特纳研究所联合发起名Stratosphere的研究项目。2014年4月,项目贡献给Apache基金会,成为孵化项目。更名为Flink2014年12月,成为基金会顶级项目2015年9月ÿ…...
No140.精选前端面试题,享受每天的挑战和学习
🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…...
Oracle 11g_FusionOS_安装文档
同事让安装数据库,查询服务器信息发现操作系统是超聚变根据华为openEuler操作系统更改的自研操作系统,安装过程中踩坑不少,最后在超聚变厂商的技术支持下安装成功,步骤可参数该文。 一、 安装环境准备 1.1 软件下载 下载地址:…...
Linux驱动实现IO模型
在Linux系统分为内核态和用户态,CPU会在这两个状态之间进行切换。当进行IO操作时,应用程序会使用系统调用进入内核态,内核操作系统会准备好数据,把IO设备的数据加载到内核缓冲区。 然后内核操作系统会把内核缓冲区的数据从内核空…...
wsl2 更新报错问题解决记录
1、问题 win10 中安装的 wsl2,启动 docker desktop 时提示 wsl2 有问题: 于是点击推荐的地址连接到微软,下载 wsl2 的更新文件。之后运行,又报错: 更新被卡住。 2、解决方法 WinR 输入 cmd 打开命令行窗口&#x…...
突破算法迷宫:精选50道-算法刷题指南
前言 在计算机科学和编程领域,算法和数据结构是基础的概念,无论你是一名初学者还是有经验的开发者,都需要掌握它们。本文将带你深入了解一系列常见的算法和数据结构问题,包括二分查找、滑动窗口、链表、二叉树、TopK、设计题、动…...
玩转Mysql系列 - 第26篇:聊聊mysql如何实现分布式锁?
这是Mysql系列第26篇。 本篇我们使用mysql实现一个分布式锁。 分布式锁的功能 分布式锁使用者位于不同的机器中,锁获取成功之后,才可以对共享资源进行操作 锁具有重入的功能:即一个使用者可以多次获取某个锁 获取锁有超时的功能ÿ…...
linux 解压缩命令tar
Tar tar 命令的选项有很多(用 man tar 可以查看到),但常用的就那么几个选项,下面来举例说明一下: tar -cf all.tar *.jpg 这条命令是将所有.jpg 的文件打成一个名为 all.tar 的包。-c 是表示产生新的包,-f 指 定包的文件名。 …...
OpenAI ChatGPT API 文档之 Embedding
译者注: Embedding 直接翻译为嵌入似乎不太恰当,于是问了一下 ChatGPT,它的回复如下: 在自然语言处理和机器学习领域,"embeddings" 是指将单词、短语或文本转换成连续向量空间的过程。这个向量空间通常被称…...
Java常用类(二)
好久不见,因工作原因,好久没发文了,OldWang 回来了,持续更新Java内容!⭐ 不可变和可变字符序列使用陷阱⭐ 时间处理相关类⭐ Date 时间类(java.util.Date)⭐ DateFormat 类和 SimpleDateFormat 类⭐ Calendar 日历类 ⭐…...
Java获取给定月份的前N个月份和前N个季度
描述: 在项目开发过程中,遇到这样一个需求,即:给定某一月份,得到该月份前面的几个月份以及前面的几个季度。例如:给定2023-09,获取该月份前面的前3个月,即2023-08、2023-07、2023-0…...
网页资源加载过程
网页资源加载是指在浏览器中访问一个网页时,浏览器如何获取和显示网页内容的过程。这个过程通常分为以下几个步骤: DNS 解析: 当用户在浏览器中输入一个网址(例如,https://www.example.com),浏览…...
使用git config --global设置用户名和邮件,以及git config的全局和局部配置
文章目录 1. 文章引言2. 全局配置2.1 命令方式2.2 配置文件方式 3. 局部配置3.1 命令方式3.2 配置文件方式 4. 总结 1. 文章引言 我们为什么要设置设置用户名和邮件? 我们在注册github,gitlab等时,一般使用用户名或邮箱: 这个用户…...
【C语言】21-指针-3
目录 1. 指针数组1.1 什么是指针数组1.2 如何定义指针数组1.3 如何使用指针数组2. 多重指针2.1 二重指针的定义2.2 二重指针的初始化与赋值2.3 二重指针的使用3. 指针常量、常量指针、指向常量的常指针3.1 概念3.2 const pointer3.3 pointer to a constant3.3.1 (pointer to a …...
解决craco启动react项目卡死在Starting the development server的问题
现象: 原因:craco.config.ts配置文件有问题 经过排查发现Dev开发模式下不能有splitChunk的配置, 解决办法: 加一个生产模式的判断,开发模式不加载splitChunk的配置,仅在生产模式才加载 判断条件代码&#…...
金华网站建设明细报价表/新网站友链
http://www.cnjm.net/tech/article2049.html 本文版权归原作者,中国JAVA手机网收录本文的目的是让更多人阅读到此文章。转载请注明出处为中国JAVA手机网<www.cnjm.net> 来自:http://www.cnjm.net/tech/article2049.html 由于无线设备所能支持的网络协议非常有…...
c 网站开发技术/电商平台推广方式有哪些
一:范围分区 就是根据数据库表中某一字段的值的范围来划分分区,例如: Sql代码 create table graderecord ( sno varchar2(10), sname varchar2(20), dormitory varchar2(3), grade int ) partition by range(grade) ( …...
厚街h5网站建设/今日国内新闻头条15条
题目: 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。 要求返回这个链表的深拷贝。 示例: 输入: {"$id":"1","next":{"$id"…...
好网站制作/淘宝指数
首先在vue.config.js文件中添加 module.exports = {publicPath:“./”}1.vue项目npm run build 得到dist文件夹 dist文件夹内容示例 2.hbuilderX中创建空的5+APP项目 新建5+APP项目 3.将vue项目中dist文件夹下的内容全部拷贝粘贴到刚刚创建的5+APP项目文件夹下(重复的就…...
做微课常用的网站有哪些/如何交换优质友情链接
香蕉派 Banana Pi BPI-M2四核开源开发板 全志H3芯片方案,也可以支持Allwinner H2 ,H3,H5芯片。Banana Pi BPI-M2Banana PI BPI-M2 是一款四核高性能单板计算机,采用全志H3处理器。Banana PI BPI-M2兼容性强大,可以跑android系统,Debian linux…...
电子商务网站建设影响因素/新品上市的营销方案
apache flinkApache flink是下一代大数据工具,也称为4G大数据。 这是真正的流处理框架(不会将流切成小批)。 Flink的内核(核心)是流式运行时,它还提供分布式处理,容错等功能。Flink以一致的高速…...