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…...
从GOPATH到Go Mod:老项目迁移必知的5个文件结构陷阱
从GOPATH到Go Mod:老项目迁移必知的5个文件结构陷阱 当Golang社区在2018年推出Go Modules时,很少有人预料到这个看似简单的包管理工具会成为Go语言发展史上的分水岭。四年后的今天,仍有大量遗留项目困在GOPATH的泥潭中,而迁移过程…...
摆脱论文困扰!高效论文写作全流程AI论文写作软件推荐(2026 最新)
论文写作全流程可拆解为文献调研→选题/开题→大纲/初稿→文献综述→降重/去AI味→润色/格式→查重/投稿七大环节,2026年AI论文写作软件按环节精准匹配,兼顾中文适配、降重能力、去AI痕迹、学术合规四大核心需求,覆盖免费/付费、通用/垂直场景…...
计算机毕业设计springboot资源分享网站 基于SpringBoot的在线知识共享与资源协作平台 SpringBoot框架下的数字化学习资料交流与社区系统
计算机毕业设计springboot资源分享网站(配套有源码 程序 mysql数据库 论文) 本套源码可以在文本联xi,先看具体系统功能演示视频领取,可分享源码参考。随着互联网技术的飞速发展和知识经济的蓬勃兴起,人们对信息获取与知识共享的需…...
永磁同步电机双矢量模型预测电流MPCC控制仿真:传统与现代控制策略的对比分析
永磁同步电机双矢量模型预测电流MPCC控制仿真【参考文献】 (1)参考文献:《永磁同步电机鲁棒双矢量模型预测电流控制_郭鑫》 (2)描述:传统单矢量预测电流控制在单个控制周期内只能输出单个电压矢量ÿ…...
VSCode远程连接报错?手把手教你修复settings.json文件(附常见错误排查)
VSCode远程连接报错终极排查指南:从settings.json修复到SSH配置优化 当你正准备通过VSCode远程连接服务器投入工作时,突然弹出的Failed to write remote.SSH.remotePlatform报错就像一盆冷水浇下来。更令人抓狂的是,明明命令行SSH连接一切正常…...
服务器频繁报soft lockup?手把手教你排查高负载进程与内核死锁问题
服务器频繁报soft lockup?手把手教你排查高负载进程与内核死锁问题 最近在运维工作中,你是否遇到过服务器突然弹出"kernel:NMI watchdog: BUG: soft lockup - CPU#X stuck for XXs!"这样的警告信息?这种内核软死锁问题看似不会立即…...
零基础入门:时空预测的系统化学习笔记
零基础入门:时空预测的系统化学习笔记 很多刚接触时序与时空预测领域的朋友,常常会陷入两个极端:要么一上来就硬啃复杂的 SOTA 模型,连基础算子都没搞懂就想复现顶会成果,最后处处碰壁;要么只停留在基础概…...
DeepSeek-OCR 2技术突破:动态视觉token重排效果展示
DeepSeek-OCR 2技术突破:动态视觉token重排效果展示 1. 引言 想象一下,当你阅读一份复杂的学术论文时,眼睛不会机械地从左上角扫到右下角,而是会自然地跳过标题、关注图表、追踪公式推导,甚至在不同的文本栏之间灵活…...
软件检测领域CNAS能力验证信息怎么查?今年有哪些软件检测领域可以参加的能力验证?
实验室在初次申请CNAS资质或者扩项时,必须要参加一次能力验证活动,并获得满意结果。对于初次申请CNAS资质的软件检测实验室,能力验证应该在质量管理体系试运行期间完成。如果时间不合适,也可以选择参加测量审核活动。测量审核活动…...
基于.NET 11 与C# 14的高性能安全客户端应用开发
基于.NET 11 与C# 14的高性能安全客户端应用开发 前言 在客户端应用开发领域,性能与安全始终是关键指标。随着.NET 11 和 C# 14 的推出,开发者拥有了更强大的工具来构建高性能且安全可靠的客户端应用。这些新技术不仅提升了应用的运行效率,还…...
