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

SpringBoot 如何优雅的进行全局异常处理

在SpringBoot的开发中,为了提高程序运行的鲁棒性,我们经常需要对各种程序异常进行处理,但是如果在每个出异常的地方进行单独处理的话,这会引入大量业务不相关的异常处理代码,增加了程序的耦合,同时未来想改变异常的处理逻辑,也变得比较困难。这篇文章带大家了解一下如何优雅的进行全局异常处理。

为了实现全局拦截,这里使用到了Spring中提供的两个注解,@RestControllerAdvice和@ExceptionHandler,结合使用可以拦截程序中产生的异常,并且根据不同的异常类型分别处理。下面我会先介绍如何利用这两个注解,优雅的完成全局异常的处理,接着解释这背后的原理。

1. 如何实现全局拦截?

1.1 自定义异常处理类

在下面的例子中,我们继承了
ResponseEntityExceptionHandler并使用@RestControllerAdvice注解了这个类,接着结合@ExceptionHandler针对不同的异常类型,来定义不同的异常处理方法。这里可以看到我处理的异常是自定义异常,后续我会展开介绍。


ResponseEntityExceptionHandler中包装了各种SpringMVC在处理请求时可能抛出的异常的处理,处理结果都是封装成一个ResponseEntity对象。ResponseEntityExceptionHandler是一个抽象类,通常我们需要定义一个用来处理异常的使用@RestControllerAdvice注解标注的异常处理类来继承自ResponseEntityExceptionHandler。ResponseEntityExceptionHandler中为每个异常的处理都单独定义了一个方法,如果默认的处理不能满足你的需求,则可以重写对某个异常的处理。

java复制代码@Log4j2  
@RestControllerAdvice  
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {  /**  * 定义要捕获的异常 可以多个 @ExceptionHandler({})     *  * @param request  request  * @param e        exception  * @param response response  * @return 响应结果  */  @ExceptionHandler(AuroraRuntimeException.class)  public GenericResponse customExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {  AuroraRuntimeException exception = (AuroraRuntimeException) e;  if (exception.getCode() == ResponseCode.USER_INPUT_ERROR) {  response.setStatus(HttpStatus.BAD_REQUEST.value());  } else if (exception.getCode() == ResponseCode.FORBIDDEN) {  response.setStatus(HttpStatus.FORBIDDEN.value());  } else {  response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());  }  return new GenericResponse(exception.getCode(), null, exception.getMessage());  }  @ExceptionHandler(NotLoginException.class)  public GenericResponse tokenExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {  log.error("token exception", e);  response.setStatus(HttpStatus.FORBIDDEN.value());  return new GenericResponse(ResponseCode.AUTHENTICATION_NEEDED);  }  }

1.2 定义异常码

这里定义了常见的几种异常码,主要用在抛出自定义异常时,对不同的情形进行区分。

java复制代码@Getter  
public enum ResponseCode {  SUCCESS(0, "Success"),  INTERNAL_ERROR(1, "服务器内部错误"),  USER_INPUT_ERROR(2, "用户输入错误"),  AUTHENTICATION_NEEDED(3, "Token过期或无效"),  FORBIDDEN(4, "禁止访问"),  TOO_FREQUENT_VISIT(5, "访问太频繁,请休息一会儿");  private final int code;  private final String message;  private final Response.Status status;  ResponseCode(int code, String message, Response.Status status) {  this.code = code;  this.message = message;  this.status = status;  }  ResponseCode(int code, String message) {  this(code, message, Response.Status.INTERNAL_SERVER_ERROR);  }  }

1.3 自定义异常类

这里我定义了一个AuroraRuntimeException的异常,就是在上面的异常处理函数中,用到的异常。每个异常实例会有一个对应的异常码,也就是前面刚定义好的。

java复制代码@Getter  
public class AuroraRuntimeException extends RuntimeException {  private final ResponseCode code;  public AuroraRuntimeException() {  super(String.format("%s", ResponseCode.INTERNAL_ERROR.getMessage()));  this.code = ResponseCode.INTERNAL_ERROR;  }  public AuroraRuntimeException(Throwable e) {  super(e);  this.code = ResponseCode.INTERNAL_ERROR;  }  public AuroraRuntimeException(String msg) {  this(ResponseCode.INTERNAL_ERROR, msg);  }  public AuroraRuntimeException(ResponseCode code) {  super(String.format("%s", code.getMessage()));  this.code = code;  }  public AuroraRuntimeException(ResponseCode code, String msg) {  super(msg);  this.code = code;  }  }

1.4 自定义返回类型

为了保证各个接口的返回统一,这里专门定义了一个返回类型。

java复制代码@Getter  
@Setter  
public class GenericResponse<T> {  private int code;  private T data;  private String message;  public GenericResponse() {};  public GenericResponse(int code, T data) {  this.code = code;  this.data = data;  }  public GenericResponse(int code, T data, String message) {  this(code, data);  this.message = message;  }  public GenericResponse(ResponseCode responseCode) {  this.code = responseCode.getCode();  this.data = null;  this.message = responseCode.getMessage();  }  public GenericResponse(ResponseCode responseCode, T data) {  this(responseCode);  this.data = data;  }  public GenericResponse(ResponseCode responseCode, T data, String message) {  this(responseCode, data);  this.message = message;  }  
}

实际测试异常

下面的例子中,我们想获取到用户的信息,如果用户的信息不存在,可以直接抛出一个异常,这个异常会被我们上面定义的全局异常处理方法所捕获,然后根据不同的异常编码,完成不同的处理和返回。

java复制代码public User getUserInfo(Long userId) {  // some logicUser user = daoFactory.getExtendedUserMapper().selectByPrimaryKey(userId);  if (user == null) {  throw new AuroraRuntimeException(ResponseCode.USER_INPUT_ERROR, "用户id不存在");  }// some logic....
}

以上就完成了整个全局异常的处理过程,接下来重点说说为什么@RestControllerAdvice和@ExceptionHandler结合使用可以拦截程序中产生的异常?

全局拦截的背后原理?

下面会提到@ControllerAdvice注解,简单地说,@RestControllerAdvice与@ControllerAdvice的区别就和@RestController与@Controller的区别类似,@RestControllerAdvice注解包含了@ControllerAdvice注解和@ResponseBody注解。

接下来我们深入Spring源码,看看是怎么实现的,首先DispatcherServlet对象在创建时会初始化一系列的对象,这里重点关注函数
initHandlerExceptionResolvers(context);.

java复制代码public class DispatcherServlet extends FrameworkServlet {// ......protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);// 重点关注initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}// ......
}


initHandlerExceptionResolvers(context)方法中,会取得所有实现了HandlerExceptionResolver接口的bean并保存起来,其中就有一个类型为ExceptionHandlerExceptionResolver的bean,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理,关键代码在这里:

java复制代码public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolverimplements ApplicationContextAware, InitializingBean {// ......private void initExceptionHandlerAdviceCache() {// ......List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());AnnotationAwareOrderComparator.sort(adviceBeans);for (ControllerAdviceBean adviceBean : adviceBeans) {ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());if (resolver.hasExceptionMappings()) {// 找到所有ExceptionHandler标注的方法并保存成一个ExceptionHandlerMethodResolver类型的对象缓存起来this.exceptionHandlerAdviceCache.put(adviceBean, resolver);if (logger.isInfoEnabled()) {logger.info("Detected @ExceptionHandler methods in " + adviceBean);}}// ......}}// ......
}

当Controller抛出异常时,DispatcherServlet通过
ExceptionHandlerExceptionResolver来解析异常,而ExceptionHandlerExceptionResolver又通过ExceptionHandlerMethodResolver 来解析异常, ExceptionHandlerMethodResolver 最终解析异常找到适用的@ExceptionHandler标注的方法是这里:

java复制代码public class ExceptionHandlerMethodResolver {// ......private Method getMappedMethod(Class<? extends Throwable> exceptionType) {List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();// 找到所有适用于Controller抛出异常的处理方法,例如Controller抛出的异常// 是AuroraRuntimeException(继承自RuntimeException),那么@ExceptionHandler(AuroraRuntimeException.class)和// @ExceptionHandler(Exception.class)标注的方法都适用此异常for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {if (mappedException.isAssignableFrom(exceptionType)) {matches.add(mappedException);}}if (!matches.isEmpty()) {/* 这里通过排序找到最适用的方法,排序的规则依据抛出异常相对于声明异常的深度,例如Controller抛出的异常是是AuroraRuntimeException(继承自RuntimeException),那么AuroraRuntimeException相对于@ExceptionHandler(AuroraRuntimeException.class)声明的AuroraRuntimeException.class其深度是0,相对于@ExceptionHandler(Exception.class)声明的Exception.class其深度是2,所以@ExceptionHandler(BizException.class)标注的方法会排在前面 */Collections.sort(matches, new ExceptionDepthComparator(exceptionType));return this.mappedMethods.get(matches.get(0));}else {return null;}}// ......
}

整个@RestControllerAdvice处理的流程就是这样,结合@ExceptionHandler就完成了对不同异常的灵活处理。

相关文章:

SpringBoot 如何优雅的进行全局异常处理

在SpringBoot的开发中&#xff0c;为了提高程序运行的鲁棒性&#xff0c;我们经常需要对各种程序异常进行处理&#xff0c;但是如果在每个出异常的地方进行单独处理的话&#xff0c;这会引入大量业务不相关的异常处理代码&#xff0c;增加了程序的耦合&#xff0c;同时未来想改…...

OSPF路由协议(红茶三杯CCNA)

链路状态路由协议 OSPF&#xff08;开放式最短路径优先&#xff09;Open Shortest Path First 是一种链路状态路由协议&#xff0c;无路由循环&#xff08;全局拓扑&#xff09;&#xff0c;RFC2328 “开放”意味着非私有的 管理型距离&#xff1a;110 OSPF采用SPF算法计算到达…...

redis中使用bloomfilter判断元素是否存在

一 bloomfiler的作用 1.1 bloomfilter的作用 由一个初始值为0的bit数组组成&#xff0c;和多个hash函数构成&#xff0c;用来判断集合中是否存在某个元素。 一个很长的二进制数组&#xff08;00000000&#xff09;一系列随机hash算法映射函数。主要用于判断一个元素是否存在…...

互联网医院系统源码实现:打造现代化医疗服务平台

摘要 本文将介绍一个基于Python的简化版互联网医院系统的源码实现&#xff0c;主要包含用户注册与登录、医生信息管理、在线预约挂号、在线问诊与咨询、电子病历管理、在线支付与结算等功能。该源码实现仅为示例&#xff0c;实际开发中需要考虑更多的业务逻辑和安全性。 1. …...

每天100w次登陆请求, 8G 内存该如何设置JVM参数?

一、新系统上线如何规划容量&#xff1f; 1.套路总结 任何新的业务系统在上线以前都需要去估算服务器配置和JVM的内存参数&#xff0c;这个容量与资源规划并不仅仅是系统架构师的随意估算的&#xff0c;需要根据系统所在业务场景去估算&#xff0c;推断出来一个系统运行模型&…...

Fiddler Everywhere(TTP调试抓包工具) for Mac苹果电脑版

Fiddler Everywhere for Mac版是Mac电脑上的一款跨平台的HTTP调试抓包工具&#xff0c;Fiddler Everywhere for Mac能够记录客户端与服务器之间的所有HTTP&#xff08;S&#xff09;通信&#xff0c;支持对包进行监视、分析、设置断点、甚至修改请求/响应数据等操作。 适用于任…...

Paragon NTFS2023最新版Mac读写NTFS磁盘工具

Paragon NTFS for Mac是Mac平台上一款非常优秀的读写工具&#xff0c;可以在Mac OS X中完全读写、修改、访问NTFS硬盘、U盘等外接设备的文件。这款软件最大的亮点简书可以让我们读写 NTFS 分区&#xff0c;因为在Mac OS X 系统上&#xff0c;默认状态下我们只能读取NTFS 分区&a…...

vs2013 32位 编译的 dll,重新用vs2022 64位编译,所遇问题记录

目录 一、vs2013 32 DLL 转 VS2022 64 DLL 所遇问题 1、 LNK2038: 检测到“_MSC_VER”的不匹配项: 值“1800”不匹配值“1900” 2、原先VS2013 现在 VS2022 导致的vsnprintf 重定义问题 3、 无法解析的外部符号 __vsnwprintf_s 4、无法解析的外部符号__imp__CertFreeC…...

Linux_CentOS_7.9部署Docker以及镜像加速配置等实操验证全过程手册

前言&#xff1a;实操之前大家应该熟悉一个新的名词DevOps 俗称开发即运维、新一代开发工程师&#xff08;Development和Operations的组合词&#xff09;是一组过程、方法与系统的统称&#xff0c;用于促进开发&#xff08;应用程序/软件工程&#xff09;、技术运营和质量保障&…...

强引用和弱引用

什么是弱引用和强引用 强引用&#xff1a; JavaScript 中强引用&#xff1a;对象的引用在 JavaScript 中是强引用&#xff0c;也就是将一个引用对象通过变量或常量保存时&#xff0c;那么这个变量或常量就是强引用&#xff0c;这个对象就不会被回收。 弱引用&#xff1a; JavaS…...

tp6 实现excel 导入功能

在项目根目录安装 composer require phpoffice/phpspreadsheet 我们看一下郊果图&#xff0c;如下 点击导入excel表格数据 出现弹窗选择文件&#xff0c;控制台打开输出文档内容 前端layui代码 <form id"uploadForm" class"form-horizontal" encty…...

【C++】类和对象(中篇)

类和对象 类的六大默认成员函数一、构造函数1. 构造函数的概念2. 构造函数的特性 二、析构函数1. 析构函数的概念2. 析构函数的特性 三、拷贝构造函数1. 拷贝构造函数的概念2. 拷贝构造函数的特征 四、赋值运算符重载1. 运算符重载2. 赋值运算符重载 五、取地址及 const 取地址…...

大数据处理架构详解:Lambda架构、Kappa架构、流批一体、Dataflow模型、实时数仓

前言 本文隶属于专栏《大数据理论体系》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见大数据理论体系 姊妹篇 《分布式数据模型详解&#xff1a;OldSQL &…...

双指针解决n数之和问题

1. 两数之和 1. 两数之和 将时间复杂度降到O(n)&#xff1b; class Solution {// 双指针public int[] twoSum(int[] nums, int target) {int nnums.length;int l0;while(l<n){int rn-1;// 找到第一个可能nums[l]nums[r]target的位置while(r>l){if(nums[l]nums[r]targe…...

安全学习DAY07_其他协议抓包技术

协议抓包技术-全局-APP&小程序&PC应用 抓包工具-Wireshark&科来分析&封包 TCPDump&#xff1a; 是可以将网络中传送的数据包完全截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤&#xff0c;并提供and、or、not等逻辑语句来帮助你去掉无用…...

electron的electron-packager打包运行和electron-builder生产安装包过程,学透 Electron 自定义 Dock 图标

electron的electron-packager打包运行和electron-builder生产安装包过程 开发electron客户端程序&#xff0c;打包是绕不开的问题。 macOS 应用构建&#xff0c;看似近在咫尺&#xff0c;实则坑坑致命。 场景&#xff1a;mac笔记本打包&#xff0c;以及生产出可交付的软件安装…...

【无标题】深圳卫视专访行云创新马洪喜:拥抱AI与云原生,深耕云智一体化创新

人工智能&#xff08;AI&#xff09;是引领新一轮科技革命和产业变革的重要驱动力。因此&#xff0c;深圳出台相关行动方案&#xff0c;统筹设立规模1,000亿元的人工智能基金群&#xff0c;引导产业集聚培育企业梯队&#xff0c;积极打造国家新一代人工智能创新发展试验区和国家…...

jenkins通过流水线进行构建jar包

前言 最近项目上需要进行CICD,本篇博客主要分享各种骚操作 目录 前言操作如下:构建触发器测试哈哈操作如下: 1.下载Jenkins.war包上传到服务器上面,然后在同级目录下面创建如下脚本: #!/bin/bash# Jenkins安装目录 JENKINS_HOME=/usr/local/jenkins# Jenkins日志文件 LO…...

Android开发:通过Tesseract第三方库实现OCR

一、引言 什么是OCR&#xff1f;OCR(Optical Character Recognition&#xff0c;光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符&#xff0c;通过检测暗、亮的模式确定其形状&#xff0c;然后用字符识别方法将形状翻译成计算机文字的过程。简单地说&#…...

合并两个有序链表——力扣21

题目描述 法一 递归 class Solution { public:ListNode* mergeTwoLists(ListNode *l1, ListNode*l2){if(l1 nullptr){return l2;} else if (l2nullptr){return l1;} else if (l1->val<l2->val){l1->next mergeTwoLists(l1->next, l2);return l1;} else {l2-&g…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

2024年赣州旅游投资集团社会招聘笔试真

2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

JS手写代码篇----使用Promise封装AJAX请求

15、使用Promise封装AJAX请求 promise就有reject和resolve了&#xff0c;就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散

前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说&#xff0c;在叠衣服的过程中&#xff0c;我会带着团队对比各种模型、方法、策略&#xff0c;毕竟针对各个场景始终寻找更优的解决方案&#xff0c;是我个人和我司「七月在线」的职责之一 且个人认为&#xff0c…...