当前位置: 首页 > news >正文

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自定义异常国际化引入封装基础异常封装基础异常扫描器&#xff0c;并注册到ExceptionHandler中项目分享以及改进点 一、SpringMVC执行流程 今天这里叙述的全…...

设计模式——7. 装饰者模式

1. 说明 装饰者模式(Decorator Pattern)是一种结构型设计模式,它允许你在不改变对象接口的前提下,动态地将新行为附加到对象上。这种模式是通过创建一个包装(或装饰)对象,将要被装饰的对象包裹起来,从而实现对原有对象功能的增强和扩展。 装饰者模式的主要特点包括:…...

安卓玩机-----反编译apk 修改apk 去广告 去弹窗等操作中的一些常识

安卓机型app的编译与反编译 apk文件的简单说明与解析 -安卓修改apk apk的组成和编译 一 电脑端几种反编译apk工具操作步骤解析 前面几个博文有说明关于反编译apk和apk架构等有些常识.今天对以上做个补充。初学者记住一点。对于一个apk文件使用压缩软件7zip打开可以查看到文件…...

Hoeffing不等式

在李航老师的统计学习方法&#xff08;第一版中&#xff09; 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​是独立随机变量&#xff0c;且 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启动时&#xff0c;会触发同步数据命令 主节点…...

k8s-实战——基于nfs实现动态存储

部署nfs服务 基于Centos7.9版本创建动态存储注意k8s版本应低于v1.24版本高k8s版本ServiceAccount需要手动创建secrets并关联相关sa部署流程 创建NFS共享服务、采用单独的节点用于nfs服务独占安装nfs-utils和rpcbindnfs客户端和服务端都安装nfs-utils包通过部署化脚本安装k8s集群…...

【广州华锐互动】鱼类授精繁殖VR虚拟仿真实训系统

随着科技的不断发展&#xff0c;虚拟现实技术在各个领域的应用越来越广泛。在养殖业中&#xff0c;VR技术可以帮助养殖户进行家鱼授精实操演练&#xff0c;提高养殖效率和繁殖成功率。本文将介绍利用VR开展家鱼授精实操演练的方法和应用。 首先&#xff0c;我们需要了解家鱼授精…...

RocketMQ Promethus Exporter

介绍​ Rocketmq-exporter 是用于监控 RocketMQ broker 端和客户端所有相关指标的系统&#xff0c;通过 mqAdmin 从 broker 端获取指标值后封装成 87 个 cache。 警告 过去版本曾是 87 个 concurrentHashMap&#xff0c;由于 Map 不会删除过期指标&#xff0c;所以一旦有 la…...

Kafka收发消息核心参数详解

文章目录 1、从基础的客户端说起1.1、消息发送者主流程1.2、消息消费者主流程 2、从客户端属性来梳理客户端工作机制2.1、消费者分组消费机制 1、从基础的客户端说起 Kafka提供了非常简单的客户端API。只需要引入一个Maven依赖即可&#xff1a; <dependency><groupId…...

Springboot中Aop的使用

Springboot中使用拦截器、过滤器、监听器-CSDN博客 相比较于拦截器&#xff0c;Spring 的aop则功能更强大&#xff0c;封装的更细致&#xff0c;需要单独引用 jar包。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-b…...

创建vue3项目、链式调用、setup函数、ref函数、reactive函数、计算和监听属性、vue3的生命周期、torefs的使用、vue3的setup写法

1 创建vue3项目 # 两种方式- vue-cli&#xff1a;vue脚手架---》创建vue项目---》构建vue项目--》工具链跟之前一样- vite &#xff1a;https://cn.vitejs.dev/-npm create vuelatest // 或者-npm create vitelatest一路选择即可# 运行vue3项目-vue-cli跟之前一样-vite 创建的…...

搭建好自己的PyPi服务器后怎么使用

当您成功搭建好自己的 PyPI 服务器后&#xff0c;您可以使用以下步骤来发布和使用您的包&#xff1a; 打包您的代码&#xff1a; 首先&#xff0c;将您的 Python 项目打包成一个发布包。确保您已经在项目根目录下创建了 setup.py 文件&#xff0c;并按照正确的格式填写了项目信…...

Vue3 中使用provide和reject

1、provide 和reject 可以实现一条事件线上的 父传子&#xff0c;父传孙等&#xff1b;相比较 props emits 仅限与父子传参更方便&#xff0c;相较于pinia书写更简单&#xff0c;但是需要注意使用响应式&#xff0c;如果是非响应式的会导致页面更新不及时 父组件 <templat…...

大数据flink篇之一-基础知识

一、起源 2010至2014年间&#xff0c;由柏林工业大学、柏林洪堡大学和哈索普拉特纳研究所联合发起名Stratosphere的研究项目。2014年4月&#xff0c;项目贡献给Apache基金会&#xff0c;成为孵化项目。更名为Flink2014年12月&#xff0c;成为基金会顶级项目2015年9月&#xff…...

No140.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…...

Oracle 11g_FusionOS_安装文档

同事让安装数据库&#xff0c;查询服务器信息发现操作系统是超聚变根据华为openEuler操作系统更改的自研操作系统&#xff0c;安装过程中踩坑不少&#xff0c;最后在超聚变厂商的技术支持下安装成功&#xff0c;步骤可参数该文。 一、 安装环境准备 1.1 软件下载 下载地址:…...

Linux驱动实现IO模型

在Linux系统分为内核态和用户态&#xff0c;CPU会在这两个状态之间进行切换。当进行IO操作时&#xff0c;应用程序会使用系统调用进入内核态&#xff0c;内核操作系统会准备好数据&#xff0c;把IO设备的数据加载到内核缓冲区。 然后内核操作系统会把内核缓冲区的数据从内核空…...

wsl2 更新报错问题解决记录

1、问题 win10 中安装的 wsl2&#xff0c;启动 docker desktop 时提示 wsl2 有问题&#xff1a; 于是点击推荐的地址连接到微软&#xff0c;下载 wsl2 的更新文件。之后运行&#xff0c;又报错&#xff1a; 更新被卡住。 2、解决方法 WinR 输入 cmd 打开命令行窗口&#x…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

SciencePlots——绘制论文中的图片

文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了&#xff1a;一行…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南

1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;使用DevEco Studio作为开发工具&#xff0c;采用Java语言实现&#xff0c;包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

C语言中提供的第三方库之哈希表实现

一. 简介 前面一篇文章简单学习了C语言中第三方库&#xff08;uthash库&#xff09;提供对哈希表的操作&#xff0c;文章如下&#xff1a; C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...