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

关于如何创建一个可配置的 SpringBoot Web 项目的全局异常处理

前情概要

这个问题其实困扰了我一周时间,一周都在 Google 上旅游,我要如何动态的设置 @RestControllerAdvice 里面的 basePackages 以及 baseClasses 的值呢?经过一周的时间寻求无果之后打算决定放弃的我终于找到了一些关键的线索。
当然在此也感激这篇文章:@ControllerAdvice的用法和原理探究
其实我们只要有调试源码的习惯,也能够发现这些东西,可能有时候就是差那么一点动力吧,比如我,就想直接看现成的解析,没有那么主动去调试源码,哈哈哈。

发现了关键问题之后

其实从上面这篇文章里我提取到的关键信息如下:

@Bean
public 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;
}

PS:😓 后来我才发现这里面的 configureHandlerExceptionResolversaddDefaultHandlerExceptionResolvers extendHandlerExceptionResolvers 这三个方法是源码里面的,我还一直在找这个博主有关这两个方法的实现。

OK 回来,这里面的关键就是我需要往 Spring IOC 里面添加一个 HandlerExceptionResolverComposite ,并且设置它的处理器列表就好了。
于是我就顺着这个思路开始捣鼓,OK,下面是第一个版本的代码:

版本一

Starter 配置类(关键代码)

@Bean
public HandlerExceptionResolver handlerExceptionResolver() {final HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();final List<HandlerExceptionResolver> resolves =Collections.singletonList(new RestGlobalExceptionHandler(starterProperties));composite.setOrder(0);composite.setExceptionResolvers(resolves);return composite;
}

Handler 处理器类(关键代码)

public final class RestGlobalExceptionHandler extends DefaultHandlerExceptionResolver implementsHandlerExceptionResolver {@Overridepublic ModelAndView resolveException(final HttpServletRequest request,final HttpServletResponse response,final Object handler,final Exception ex) {ModelAndView view = new ModelAndView();if (ex instanceof BusinessException) {printToResponse(response, handlerError(request, (BusinessException) ex));} else if (ex instanceof MethodArgumentNotValidException) {printToResponse(response, handlerError(request, (MethodArgumentNotValidException) ex));} else {// use default exception handlerview = super.doResolveException(request, response, handler, ex);}if (Objects.isNull(view)) {// use finally exception handlerview = new ModelAndView();printToResponse(response, handlerError(request, ex));}return view;}// handlerError 以及 printToResponse 方法省略
}

这里说下为什么就要继承 DefaultHandlerExceptionResolver以及实现HandlerExceptionResolver接口:
实现接口:因为 HandlerExceptionResolverComposite类的 resolves 列表就是一个List<HandlerExceptionResolver>, 所以我们自定义的 Handler 需要实现这个接口。
继承 DefaultHandlerExceptionResolver类:因为可以看到我重写了 resolveException这个方法

但是这还存在问题:

// 这个是 DefaultHandlerExceptionResolver 的 doResolveException 方法,你们实践第一版的时候会发现,有一些异常信息被这方法处理了,但是我发现如果使用 @RestControllerAdvice 结合 @ExceptionHandler 的话,优先是考虑我们自定义的异常处理的,于是有了下面的版本二。
@Nullableprotected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {try {if (ex instanceof HttpRequestMethodNotSupportedException) {return this.handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException)ex, request, response, handler);}if (ex instanceof HttpMediaTypeNotSupportedException) {return this.handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException)ex, request, response, handler);}if (ex instanceof HttpMediaTypeNotAcceptableException) {return this.handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException)ex, request, response, handler);}if (ex instanceof MissingPathVariableException) {return this.handleMissingPathVariable((MissingPathVariableException)ex, request, response, handler);}if (ex instanceof MissingServletRequestParameterException) {return this.handleMissingServletRequestParameter((MissingServletRequestParameterException)ex, request, response, handler);}if (ex instanceof ServletRequestBindingException) {return this.handleServletRequestBindingException((ServletRequestBindingException)ex, request, response, handler);}if (ex instanceof ConversionNotSupportedException) {return this.handleConversionNotSupported((ConversionNotSupportedException)ex, request, response, handler);}if (ex instanceof TypeMismatchException) {return this.handleTypeMismatch((TypeMismatchException)ex, request, response, handler);}if (ex instanceof HttpMessageNotReadableException) {return this.handleHttpMessageNotReadable((HttpMessageNotReadableException)ex, request, response, handler);}if (ex instanceof HttpMessageNotWritableException) {return this.handleHttpMessageNotWritable((HttpMessageNotWritableException)ex, request, response, handler);}if (ex instanceof MethodArgumentNotValidException) {return this.handleMethodArgumentNotValidException((MethodArgumentNotValidException)ex, request, response, handler);}if (ex instanceof MissingServletRequestPartException) {return this.handleMissingServletRequestPartException((MissingServletRequestPartException)ex, request, response, handler);}if (ex instanceof BindException) {return this.handleBindException((BindException)ex, request, response, handler);}if (ex instanceof NoHandlerFoundException) {return this.handleNoHandlerFoundException((NoHandlerFoundException)ex, request, response, handler);}if (ex instanceof AsyncRequestTimeoutException) {return this.handleAsyncRequestTimeoutException((AsyncRequestTimeoutException)ex, request, response, handler);}} catch (Exception var6) {Exception handlerEx = var6;if (this.logger.isWarnEnabled()) {this.logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);}}return null;}

版本二

这里主要解决的是,有一些自定义的异常处理提前被 DefaultHandlerExceptionResolver 类的 doResolveException 的方法处理了,了解 Spring IOC 容器的小伙伴应该都知道,这很容易让我们联想起,Bean 的顺序问题,那么我们哪里设置了我们自定义 Bean 的顺序呢,也就是使用 @Order 或者代码里面设置了,没错,就是这里:

@Bean
public HandlerExceptionResolver handlerExceptionResolver() {final HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();final List<HandlerExceptionResolver> resolves =Collections.singletonList(new RestGlobalExceptionHandler(starterProperties));// 这里,这里,这里composite.setOrder(0);composite.setExceptionResolvers(resolves);return composite;
}

但是我们要怎么知道我们具体要设置的值是什么呢?大家都知道在使用 Spring 的时候设置 Bean 的 Order 值越小那么它的优先级越高,所以我尝试着把 0 换成 -1,试试,结果还真成功了,我们自定义的优先执行了,但是作为一个搞技术的人,还是想摸清楚它的原理吧,为啥设置成 -1 就可以了呢?
一开始没有啥头绪,不知道怎么去查找 Spring 配置异常处理这块( 主要还是源码不熟 -_-! ),但后面想一想,把目标换一下不就好了,是因为 DefaultHandlerExceptionResolver 这个 Bean 的原因,那我就找 Spring 是哪里把他放进 Spring IOC 容器的不就好了。因为我使用的框架是 SpringBoot,所以相关的配置肯定在 XXXAutoConfiguration 类里面,于是就找找找…

  • WebMvcAutoConfiguration 没有…
  • DispatcherServletAutoConfiguration 没有…

好吧最后还是借助 IntelliJ 这个工具,因为我们自定义的是一个 HandlerExceptionResolverComposite Bean,所以我们进入这个类:
在这里插入图片描述
发现它实现了 HandlerExceptionResolver 这个接口,一般 Spring 都会使用接口作为一个接收实现的变量,然后 return 回去交给 Spring IOC 容器,所以我们再进入这个接口:
在这里插入图片描述
利用 IntelliJ 的工具,找到哪里注入了这个 Bean:
在这里插入图片描述
在这里插入图片描述
进去之后发现了跟我们类似的代码:
在这里插入图片描述
看到 618 行的 this.addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager); 吗?这个就是添加 DefaultHandlerExceptionResolver 的地方,并且设置的 Order 是 0,如果 Debug 的话你会发现这个方法除了加入 DefaultHandlerExceptionResolver 之外,还会加入自定义使用注解的异常处理器,但是它在 List 中的顺序比 DefaultHandlerExceptionResolver 靠前,所以它会优先使用自定义的处理器处理,但是使用注解是 Spring 自己处理的,然后加入这个 List 中,我们没办法去修改这个 List,但是我们知道了可以自定义 HandlerExceptionResolverComposite 并且把它的 Order 设置成比 0 小就好了,所以这就是为什么我们设置成 -1 就可以覆盖 DefaultHandlerExceptionResolver 的行为的原因。

所有的代码已经放在我的代码仓库:码云,欢迎来访以及给我小⭐⭐

相关文章:

关于如何创建一个可配置的 SpringBoot Web 项目的全局异常处理

前情概要 这个问题其实困扰了我一周时间&#xff0c;一周都在 Google 上旅游&#xff0c;我要如何动态的设置 RestControllerAdvice 里面的 basePackages 以及 baseClasses 的值呢&#xff1f;经过一周的时间寻求无果之后打算决定放弃的我终于找到了一些关键的线索。 当然在此…...

docker三种自定义网络(虚拟网络) overlay实现原理

docker提供了三种自定义网络驱动&#xff1a;bridge、overlay、macvlan。 bridge驱动类似默认的bridge网络模式。 overlay和macvlan是用于创建跨主机网络。 支持自定义网段、网关&#xff0c;docker network create --subnet 172.77.0.0/24 --gateway 172.77.0.1 my_n…...

C#上位机1ms级高精度定时任务

precisiontimer 安装扩展包 添加引用 完整代码 using PrecisionTiming;using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; us…...

盘点28个免费域名申请大全

盘点28个免费域名申请大全 免费域名推荐学习使用&#xff0c;免费就意味着没任何保障。 名称稳定时间支持解析模式后缀格式说明地址EU.org28 年NS.eu.org/. 国家简写.eu.org需要审核&#xff0c;稳定性高&#xff0c;限制少&#xff0c;国内访问有问题&#xff0c;可 CFeu.orgp…...

【vue】封装的天气展示卡片,在线获取天气信息

源码 <template><div class"sen_weather_wrapper"><div class"sen_top_box"><div class"sen_left_box"><div class"sen_top"><div class"sen_city">山东</div><qctc-time cl…...

【MySQL】库的操作和表的操作

库的操作和表的操作 一、库的操作1、创建数据库(create)2、字符集和校验规则&#xff08;1&#xff09;查看系统默认字符集以及校验规则&#xff08;2&#xff09;查看数据库支持的字符集&#xff08;3&#xff09;查看数据库支持的字符集校验规则&#xff08;4&#xff09;校验…...

【学习笔记】后端(Ⅰ)—— NodeJS(Ⅱ)

NodeJS 3、进阶篇 —— Express框架 3.1、Express 框架介绍 3.2、Express 框架初体验 3.3、使用 3.4、中间件 3.5、托管静态文件 3.6、获取表单数据 3.7、防盗链 3.8、路由模式化 3.8、EJS 模板引擎 3.9、express-generator…...

VMware报平台不支持虚拟化Win10家庭版关闭Hyper-V及内核隔离

1.BIOS中开启虚拟化功能 2.启动或关闭程序中找不到Hyper-v 停止 hypervisorlaunchtype&#xff08;Windows Hyper-V 启动加载器&#xff09; 以管理员的身份打开命令行窗口&#xff0c;运行如下命令&#xff0c;关闭停止 Windows Hyper-V 启动加载器 开启 Windows Hyper-V 启…...

简单介绍十款可以免费使用的API测试工具

API开发应该是后端开发最常见的工作&#xff0c;而调试和测试API是非常关键的&#xff0c;这篇文章简单介绍几款常用的工具以供大家参考。 SoapUI SoapUI是很老牌的工具的&#xff0c;在之前Webservice盛行的时候经常会用到。 现在官方推出了Pro版本的ReadyAPI&#xff0c;但要…...

非授权人员进入报警系统

非授权人员进入报警系统基于智能视频分析技术和深度学习技术&#xff0c;非授权人员进入报警系统通过现场已经装好的监控摄像头针对人体进行精准检测&#xff0c;并根据设置的禁入区范围进行判断。通过图像处理和人体识别算法&#xff0c;非授权人员进入报警系统可以在实时监测…...

Mysql基础教程(03):AND

MySQL AND 运算符的用法 本文介绍了 MySQL 中如何在 WHERE 子句中使用 AND 运算符组合多个查询条件过滤查询数据。 当使用 SELECT 查询数据时&#xff0c;如果 WHERE 子句中有多个条件&#xff0c;可以根据需要使用 AND, OR, 或者 NOT 运算符将他们组合起来。本文主要介绍 AN…...

为什么要使用 eval

调用 eval 方法的原因是为了确保模型在进行预测时使用正确的配置。在训练过程中&#xff0c;某些层&#xff08;如 Dropout 层&#xff09;的行为是为了正则化而设计的&#xff0c;它们会在每次迭代中随机丢弃一些神经元的输出。而在评估模式下&#xff0c;这些层将不再随机丢弃…...

BCD编码(8421)介绍

概念 BCD (Binary-Coded Decimal) 是一种二进制的数字编码形式&#xff0c;其特点每个十进制数位用4个二进制位来表示。 在网络IO中&#xff0c;你传输一个数字类型最少需要一字节&#xff0c;传输两个数字类型最少需要两字节&#xff0c;但是当你使用BCD编码后传输&#xff…...

前端javascript包管理,npm升级用pnpm

一 pnpm 介绍 pnpm&#xff08;Package Manager&#xff09;是一个快速、节省磁盘空间的 JavaScript 包管理器&#xff0c;它是 Node.js 生态系统中 npm 的一个替代品。pnpm 解决了传统包管理工具在处理依赖时的一些痛点&#xff0c;特别是关于存储空间使用和依赖地狱的问题。…...

数据库操作(函数)

函数是一段可以直接被另外一段程序调用的程序或代码 一。字符串函数 1.concat(s1,s1....sn)&#xff1a;字符串拼接&#xff0c;将s1&#xff0c;s2&#xff0c;sn拼接为一个字符串 例如&#xff1a; select concat("hello","world"); 2.lower(str&…...

[建堆堆排序的时间复杂度推导]向上建堆向下建堆堆排序的时间复杂度分析推导

&#x1f496;&#x1f496;&#x1f496;欢迎来到我的博客&#xff0c;我是anmory&#x1f496;&#x1f496;&#x1f496; 又和大家见面了 欢迎来到动画详解数据结构系列 作为一个程序员你不能不掌握的知识 先来自我推荐一波 个人网站欢迎访问以及捐款 推荐阅读 如何低成本搭…...

【C++初阶】--- C++入门(上)

目录 一、C的背景及简要介绍1.1 什么是C1.2 C发展史1.3 C的重要性 二、C关键字三、命名空间2.1 命名空间定义2.2 命名空间使用 四、C输入 & 输出 一、C的背景及简要介绍 1.1 什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&…...

安装和使用图像处理软件GraphicsMagick @FreeBSD

GraphicsMagick是一个用于处理图像的读取、写入和操作的工具软件。它被誉为图像处理领域的“瑞士军刀”&#xff0c;短小精悍&#xff0c;支持超过88种图像格式&#xff0c;包括DPX、GIF、JPEG、JPEG-2000、PNG、PDF、PNM和TIFF等。 GraphicsMagick的主要特点包括&#xff1a;…...

一款功能强大的安卓虚拟机应用——VMOS Pro使用分享

前段时间我刚刚分享一个WeChat平板模块能够允许用户自由修改系统设置&#xff0c;让你的Android备用手机焕发新生&#xff0c;实现手机PAD化&#xff0c;实现两台设备同时登录微信号。今天我分享的这个相比WeChat更为简单&#xff0c;因为它可以通过虚拟机的方式进行多种androi…...

【408真题】2009-12

“接”是针对题目进行必要的分析&#xff0c;比较简略&#xff1b; “化”是对题目中所涉及到的知识点进行详细解释&#xff1b; “发”是对此题型的解题套路总结&#xff0c;并结合历年真题或者典型例题进行运用。 涉及到的知识全部来源于王道各科教材&#xff08;2025版&…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

Python:操作 Excel 折叠

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...