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!多沟通,一起学习,一起进步!
如果喜欢本篇文章,点个赞吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/145002.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于Java的药品管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

Java微信分享接口开发

概述 微信JS-SDK实现自定义分享功能&#xff0c;分享给朋友&#xff0c;分享到朋友圈 详细 概述 概述 微信公众平台开始支持前端网页&#xff0c;大家可能看到很多网页上都有分享到朋友圈&#xff0c;关注微信等按钮&#xff0c;点击它们都会弹出一个窗口让你分享和关注&…

前端uniapp防止页面整体滑动页面顶部以上,设置固定想要固定区域宽高

解决&#xff1a;设置固定想要固定区域宽高 目录 未改前图未改样式改后图改后样式 未改前图 未改样式 .main {display: flex;flex-direction: row;// justify-content: space-between;width: 100vw;// 防止全部移动到上面位置&#xff01;&#xff01;&#xff01;&#xff01…

【C++的OpenCV】第十三课-OpenCV基础强化(一):绝对有用!Mat相关的一系列知识(基础->进阶)

&#x1f389;&#x1f389;&#x1f389; 欢迎各位来到小白 p i a o 的学习空间&#xff01; \color{red}{欢迎各位来到小白piao的学习空间&#xff01;} 欢迎各位来到小白piao的学习空间&#xff01;&#x1f389;&#x1f389;&#x1f389; &#x1f496;&#x1f496;&…

在nodejs中如何防止ssrf攻击

在nodejs中如何防止ssrf攻击 什么是ssrf攻击 ssrf&#xff08;server-side request forgery&#xff09;是服务器端请求伪造&#xff0c;指攻击者能够从易受攻击的Web应用程序发送精心设计的请求的对其他网站进行攻击。(利用一个可发起网络请求的服务当作跳板来攻击其他服务)…

C++核心编程--继承篇

4.6、继承 继承是面向对象三大特征之一 有些类与类之间存在特殊的关系&#xff0c;例如下图中&#xff1a; ​ 我们发现&#xff0c;定义这些类的定义时&#xff0c;都拥有上一级的一些共性&#xff0c;还有一些自己的特性。那么我们遇到重复的东西时&#xff0c;就可以考虑使…

大数据Flink(八十九):Temporal Join(快照 Join)

文章目录 Temporal Join(快照 Join) Temporal Join(快照 Join) Temporal Join 定义(支持 Batch\Streaming):Temporal Join 在离线的概念中其实是没有类似的 Join 概念的,但是离线中常常会维护一种表叫做 拉链快照表,使用一个明细表去 join 这个 拉链快照表 的 join …

GD32F10x的输出模式

1. 单片机型号的识别。 2. GPIO的输出模式。 1. 开漏模式 2.推挽模式 3.复用开漏模式 4.复用推挽模式。 开漏模式&#xff1a;&#xff08;写入位设置&#xff0c;输出数据寄存器来控制MOS&#xff09; 只有N-MOS管导通。PMOS不导通。 当N-MOS的栅极为0&#xff0c;N-MOS管…

SQL血缘解析原理

根据sql解析获取到表到表, 字段到字段间的关系,即血缘关系。实际上这是从sql文本获取到数据流的过程。 大致步骤如下&#xff1a; 1.sql文本进行词法分析 2.sql语法分析获取到AST抽象语法树 3.访问AST抽象语法树根据语法结构推测出数据的流向,例如create as select from 这种结…

排序:败者树和置换选择排序(解决外部排序中的优化问题)

1.算法目的&#xff08;败者树&#xff09; 解决多路平衡归并带来的问题。 在外部排序中&#xff0c;使用k路平衡归并策略, 选出一个最小元素需要对比关键字(k-1)次&#xff0c; 导致内部归并所需时间增加。&#xff08;可用“败者树”进行优化&#xff09; 2.败者树的定义 …

高德地图根据两点的经纬度计算两点之间的距离(修正版)

SQL语句可以用来计算两个经纬度之间的距离。下面是一个示例的SQL语句&#xff1a; SELECT id, ( 6371 * ACOS( COS( RADIANS( lat1 ) ) * COS( RADIANS( lat2 ) ) * COS( RADIANS( lng2 ) - RADIANS( lng1 ) ) SIN( RADIANS( lat1 ) ) * SIN( RADIANS( lat2 ) ) ) ) AS dista…

ROS系统读取USB相机图像数据

ROS系统读取USB相机图像数据 前言usb_cam 功能包下载与编译摄像头选择连接摄像头可配置参数 前言 usb_cam功能包简介 为了丰富机器人与外界的交互方式&#xff0c;已经增加了与机器人的语音交互方式&#xff0c;不仅使机器人能够说话发声&#xff0c;还能听懂我们说的话&#…

泽众APM性能监控软件

泽众Application Performance Management&#xff08;简称APM&#xff09;是一款专业的性能监控工具&#xff0c;可以对全链路如Web服务器、应用服务器、数据库服务器等进行实时监控&#xff0c;并以图表化的形式直观地呈现监控数据&#xff0c;为系统性能优化和定位问题提供准…

3 OpenCV两张图片实现稀疏点云的生成

前文&#xff1a; 1 基于SIFT图像特征识别的匹配方法比较与实现 2 OpenCV实现的F矩阵RANSAC原理与实践 1 E矩阵 1.1 由F到E E K T ∗ F ∗ K E K^T * F * K EKT∗F∗K E 矩阵可以直接通过之前算好的 F 矩阵与相机内参 K 矩阵获得 Mat E K.t() * F * K;相机内参获得的方式…

ThreeJS-3D教学四-光源

three模拟的真实3D环境&#xff0c;一个非常炫酷的功能便是对光源的操控&#xff0c;之前教学一中已经简单的描述了多种光源&#xff0c;这次咱们就详细的讲下一些最常见的光源&#xff1a; AmbientLight 该灯光在全局范围内平等地照亮场景中的所有对象。 该灯光不能用于投射阴…

蓝桥杯每日一题2023.9.29

蓝桥杯大赛历届真题 - C&C 大学 B 组 - 蓝桥云课 (lanqiao.cn) 题目描述1 题目分析 看见有32位&#xff0c;我们以此为入手点&#xff0c; B代表字节1B 8b b代表位&#xff0c;32位即4个字节 (B) 1KB 1024B 1MB 1024KB (256 * 1024 * 1024) / 4 67108864 故答案…

Spring | 基于SpringBoot的多数据源实战 - 使用seata实现多数据源的全局事务管理

Spring | 基于SpringBoot的多数据源实战 - 使用seata实现多数据源的全局事务管理 引言1.1 多数据源的必要性1.2 多数据源的应用场景 实战演示2.1 创建实体类2.2 配置数据源2.3 实现数据源配置类2.4 配置Repository类2.5 运行与验证 事务管理与数据一致性3.1 事务管理3.2 使用Se…

MySQL 索引底层 B+Tree 原理解析

目录 一、前言二、B-Tree 和 BTree 的区别三、InnoDB 和 MyISAM 存储引擎索引存储区别MyISAMInnoDB 四、InnoDB 联合索引底层数据结构五、MySQL 中三次磁盘IO最大能检索多少数据 一、前言 索引是帮助高效获取数据排好序的数据结构&#xff0c;任何数据库都会使用到索引&#x…

C++之容器类有趣的实验(二百四十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

uniapp:如何修改路由加载条的样式

路由加载条默认是绿色&#xff0c;通过元素选择工具我们可以知道元素的类名是#router-loadding .loadding&#xff0c;具体设置在h5-dom.js文件里。 我们直接在App.vue里对加载样式进行修改即可&#xff0c;如下图&#xff1a; !important一定记得要加&#xff0c;否则不生效&…