SpringBoot异常处理?用这两个就够啦!
在日常项目中,我们难免会遇到系统错误的情况。如果对系统异常的情况不做处理,Springboot本身会默认将错误异常作为接口的请求返回。
@GetMapping("/testNorError")
public void testNorError() {try {throw new MyException(6000, "我的错误");}catch (Exception e){throw new MyException(5000, "我的包装异常", e);}
}
从上图可以看到,Springboot没有对异常进行处理的情况下,将错误的堆栈直接当做响应数据返回了。这样对用户既不友好,又可能因为泄漏系统堆栈信息引发潜在的安全风险。因此,搭建一个完善的异常处理机制,对于维护系统健壮性是十分必要的。
通用异常处理
要快速的搭建异常处理机制,那么需要考虑如何对异常进行捕获并加以处理?最便捷的方法便是用**@ExceptionHandler**注解实现。
@ExceptionHandler(MyException.class)protected ResponseEntity<Object> handleException(Exception ex) {LOGGER.error("Failed to execute,handleException:{}", ex.getMessage(), ex);return new ResponseEntity<>(new ResultDTO().fail(ResultCodeEnum.ERROR_SERVER), HttpStatus.OK);}
通过在Controller内添加上述的异常处理代码,Springboot就可以将相关的错误信息转义成系统的统一错误处理,进而避免堆栈外露。(这里的ResultDTO是系统内自定义的JSON结构,可以根据自己的业务自行修改。)
然而,@ExceptionHandler本身存在一个弊端,就是他作用的范围必须是Controller,也就意味着有多少个Controller,你的异常处理代码便要重复写多遍,这无疑是低效率的。为了减少重复的代码冗余,@ControllerAdvance就进入了我们的视野。
@ControllerAdvice
@Slf4j
public class ExtGlobalExceptionHandler {@ExceptionHandler(Exception.class)protected ResponseEntity<Object> handleException(Exception ex) {LOGGER.error("Failed to execute,handleException:{}", ex.getMessage(), ex);return new ResponseEntity<>(new ResultDTO().fail(ResultCodeEnum.ERROR_SERVER), HttpStatus.OK);}
}
简单来说,@ControllerAdvance是一个全局处理的注解,其中的代码会对所有的Controller生效,通常会搭配@ExceptionHandler处理异常,由此以来就可以实现只编写一次异常处理方法就可以处理全局异常的情况。
至于@ControllerAdvance和@ExceptionHandler是如何实现这个神奇的功能的,限于篇幅原因,后续会考虑单独出一篇文章详细介绍。(其实根据名字,不难推断ControllerAdvance就是一种针对于Controller对象的动态代理罢了。)
个性化异常处理
用了@ControllerAdvance和ExceptionHandler,几乎可以解决80%的项目面临的报错处理问题。然而,思考一下。如果一个项目中出现了多组人同时维护、迭代一个系统的时候(降本增效嘛,懂的都懂),每组人要关注的报错自然会不一样。如A组人只关注报错A,B组人员只关注报错B,那么这种通用的异常解决方案是无法区分开的。
针对于这种情况,就不得不请出另外一位大佬了,他就是:AOP,针对于动态代理有很多的实现方式和框架,这里我们直接默认采用SpringBoot的自带AOP框架:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>2.1.11.RELEASE</version>
</dependency>
不管选择的AOP实现框架是什么,要采用AOP编码都少不了以下两个步骤:
1、定义切点和执行时机(哪些地方要做增强)
2、定义通知(要怎么增强)
定义切点和执行时机
对于Springboot自带的AOP框架,其执行时机共有以下五个:
增强时机 | 增强类型 | 异同点 |
---|---|---|
@After | 后置增强 | 目标方法执行之后调用增强方法 |
@Before | 前置增强 | 目标方法执行之前先调用增强方法 |
@AfterReturning | 返回增强 | 目标方法执行return之后返回结果之前调用增强方法,如果出异常则不执行 |
@AfterThrowing | 异常增强 | 目标方法执行产生异常调用增强方法,需注意的是,处理后异常依旧会往上抛出,不会被catch。 |
@Around | 环绕增强 | 环绕增强包含前面四种增强,通过一定的try-catch处理,环绕类型可以替代上述的任意一种增强。 |
了解了SpringBoot的动态代理的执行时机之后,我们还需要知道其定义切点的方式。框架定义切点的方式主要有两个:
-
切点表达式
-
注解
注释
我们首先介绍注释的正确打开方式。要通过注解来实现自己的AOP,那么首先需要定义一个新的注解。这里我简单定义了一个注解:
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {String SERVER_NAME() default "";String action() default "";
}
在定义了注解以后,将注解定义为方法的入参,并通过@annotation()标注出注解的变量名称,由此就可以实现注解AOP的功能。
//处理注解的地方
@Around(value = "@annotation(name)")
public <T> T test(ProceedingJoinPoint point, MyAnnotation name) throws Throwable {String serverName = name.SERVER_NAME();//处理异常return handlerRpcException(point, serverName);
}//具体代码执行处
@MyAnnotation(SERVER_NAME = "下游系统", action = "操作处理")
public <T> T testFunction() {return (T) new ResultDTO<>().success(Boolean.TRUE);
}
切点表达式
Springboot的AOP中,还提供了一种十分强大的实现动态代理切点标注的方式,即切点表达式,其基本模式如下所示:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
注意到modifiers-pattern?、declaring-type-pattern?、throws-pattern?等携带问号的参数都是非必填的。紧接着我们来逐一介绍上述参数的含义:
- modifiers-pattern?:修饰符匹配,主要表示的是切点是public/private/protected/default的哪一种。
- ret-type-pattern:顾名思义,指的是返回值的类型,常见如:void/Boolean/String等
- declaring-type-pattern?:这个指的是被增强的方法、属性的类路径,如com.example.demo.service.aop.MyAspect等
- name-pattern(param-pattern):这个是相对关键的参数,指的是被增强的方法名称以及其对应的参数类型。
- throws-pattern:throw-pattern见词知意,可以知道它是指的方法所抛出的异常类型。
除了了解了上述的表达式的基本匹配含义以外,还有几个特殊的符号通配指的提一下:
*****:匹配任何数量字符
…:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数(0个或者多个参数)
+:匹配指定类型及其子类型;仅能作为后缀放在类型模式后边
也许上面的代码和介绍让你一脸懵逼,没关系,可以简单看下下面两个表达式的含义,你就大致明白他们的含义了:
// 1、代表【返回值任意】且前缀为【com.example.demo.rpc】的【任意类下】【任意名称】的【所有参数】方法
execution(* com.example.demo.rpc.*.*(..))// 2、代表【返回值为Boolean】且位于【com.example.demo.rpc及其子包下】的【任意名称】的【以String为最后一个入参数】的方法
execution(Boolean com.example.demo.rpc..*(.., String))
借助于切面表达式,我们可以很自由灵活地定义出我们的切点,从而通过AOP实现我们对于异常的处理
@Pointcut("execution(Boolean com.example.demo.rpc..*(.., String)) || execution(另外一个表达式)")private void PointCutOfAnno() {
}@Around(value = "PointCutOfAnno()")
public <T> T testForAOP(ProceedingJoinPoint point) throws Throwable {//处理对应的异常return handlerRpcException(point, serverName);
}
总结
本文介绍了两种Springboot下针对于异常处理的编写方法:
一、借助于@ControllerAdvance和@ExceptionHandler实现的通用异常处理方法
二、借助于AOP实现的个性化异常处理机制。
两者其实本质上的实现思路都是一样的,通过对执行代码做动态代理,从而将错误包装起来,达到异常不外漏的效果。在实际业务场景中,方法一几乎可以涵盖80%的异常处理场景。方案二则主要针对一个系统中需要做个性化处理的情况,可以根据具体的业务需要进行选择。
参考文献
@Pointcut 的 12 种用法,你知道几种?
相关文章:

SpringBoot异常处理?用这两个就够啦!
在日常项目中,我们难免会遇到系统错误的情况。如果对系统异常的情况不做处理,Springboot本身会默认将错误异常作为接口的请求返回。 GetMapping("/testNorError") public void testNorError() {try {throw new MyException(6000, "我…...
mysql-查询重复数据的条数-count
查询重复数据的条数 select name , count(*) from table group by name; 查询结果:查询表table中name相同重复的个数 补充:count的用法 查询一个表中总共多少行(多少条数据) select count (*) from table 小结 …...

【Java枚举类】使用enum关键词定义枚举类
使用说明 1.使用 enum 定义的枚举类默认继承了 java.lang.Enum类,因此不能再继承其他类 2.枚举类的构造器只能使用 private 权限修饰符 3.枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。列出的 实例系统会自动添加 public static final 修饰 4.必须在…...

第十四届蓝桥杯三月真题刷题训练——第 8 天
目录 第 1 题:分数 题目描述 运行限制 代码: 第 2 题:回文日期 题目描述 输入描述 输出描述 输入输出样例 运行限制 代码: 第 3 题:迷宫 代码: 第 1 题:分数 题目描述 本题为填空题…...

鼎阳SDS2074X Plus免费“升级”(破解)备忘录
鼎阳SDS2074X Plus从基础参数来看,在一众国产示波器里并不出彩。但作为一款可以免费“升级”到【1】4通道2GSa/s的采样率,500MHz分析带宽,200Mpts存储深度的数字示波器(可惜原配的是200MHz的探头,500MHz的探头还是贵&a…...

【C++】C++标准模板库STL (一) string类的使用详解
前言 在前一章种我们介绍了C中的模板的使用,这是一种泛型编程,模板的使用能让我们减少大量的相似代码,减少我们的代码量与工作量,写出更加高效简洁的代码,模板如此好用,但还是要我们先出写一个泛型类或函数…...

如何用SpringBoot+Thymeleaf+Echart生成好看的柱状图,折线图,饼状图
一、前言 上篇文章我们用POI技术读取Excel并生成了相应的图表。但是实际的效果比较一般,因为本身WPS生成图表就比较简单,如果用程序操作远比人工耗时费力,效果远不如一些付费模板。如下图所示: 然后我就想到前端不是有一个简单易…...
LeetCode819. 最常见的单词(python)
题目 给定一个段落 (paragraph) 和一个禁用单词列表 (banned)。返回出现次数最多,同时不在禁用列表中的单词。 题目保证至少有一个词不在禁用列表中,而且答案唯一。 禁用列表中的单词用小写字母表示,不含标点符号。段落中的单词不区分大小写。…...

【深入理解C指针】经典笔试题——指针和数组
🔹内容专栏:【C语言】进阶部分 🔹本文概括:一些指针和数组笔试题的解析 。 🔹本文作者:花香碟自来_ 🔹发布时间:2023.3.12 目录 一、指针和数组练习题 1. 一维数组 2. 字符数组 …...
雷达散射截面
雷达散射截面(Radar Cross Section, RCS)是表征目标散射强弱的物理量。 σ = 4 π R 2 ∣ E s ∣ 2 ∣ E i ∣ 2 \sigma = 4\pi R^2 \frac{|E_s |^2}{|E_i|^2}...
希腊棺材之谜——复盘
文章目录梗概推导伪解答虽然花费6-8小时来看小说,是一件很奢侈的事情。但是再荒诞的事情终归有它背后的逻辑链条。这正如Ellery所坚持的那样,逻辑为王。希腊棺材之谜是Ellery Queen首次展露头角, 因此作者特地给他安排了3次伪解答和1次真解答…...

CentOS的下载和安装
文章目录前言一、CentOS的下载二、如何下载1.选择下载版本2.选择isos3.点击isos后,进入如下页面,接着点击X86_644.一般选择下面框住的进行下载三、安装软件选择设置接着进行分区设置设置网络和主机名前言 在学习Linux时,记录下CentOS的安装 …...

new bing的chatGPT如何解析英文论文pdf
昨天我的new bing申请下来了,有了聊天的界面: 但是解析pdf的英文文献,还是不行,没有对话窗口。就问了一下chatGPT,方案如下: 要使用New Bing解析PDF文献,你需要以下几个步骤: 1&a…...

学会这12个Python装饰器,让你的代码更上一层楼
学会这12个Python装饰器,让你的代码更上一层楼 Python 装饰器是个强大的工具,可帮你生成整洁、可重用和可维护的代码。某种意义上说,会不会用装饰器是区分新手和老鸟的重要标志。如果你不熟悉装饰器,你可以将它们视为将函数作为输…...

企业使用ERP的好处
ERP系统是企业管理信息系统的简称,它是以信息技术为手段,以物流、资金流、信息流为主线,以企业的核心业务流程为对象,建立的一套适用于企业管理的、高效的企业管理信息系统。它是通过科学方法和计算机信息技术,将企业运…...
【QT】如何获取屏幕(桌面)的大小或分辨率
目录1. QDesktopWidget 获取系统屏幕大小2. QScreen 获取系统屏幕大小3. geometry() 与 availableGeometry() 的区别1. QDesktopWidget 获取系统屏幕大小 QDesktopWidget 提供了详细的位置信息,其能够自动返回窗口在用户窗口的位置和应用程序窗口的位置 QDesktopW…...
ETL工具的选择
正确选择 ETL 工具,可以从 ETL 对平台的支持、对数据源的支持、数据转换功能、管理 和调度功能、集成和开放性、对元数据管理等功能出发,具体如下。 支持平台 随着各种应用系统数据量的飞速增长和对业务可靠性等要求的不断提高,人们对数据抽…...

SpringBoot仿天猫商城java web购物网站的设计与实现
1,项目介绍 基于 SpringBoot 的仿天猫商城拥有两种角色,分别为管理员和用户。 迷你天猫商城是一个基于SSM框架的综合性B2C电商平台,需求设计主要参考天猫商城的购物流程。 后端页面兼容IE10及以上现代浏览器,Chrome,Edge,Firebox…...
C#基础教程22 文件的输入与输出
C# 文件的输入与输出 一个 文件 是一个存储在磁盘中带有指定名称和目录路径的数据集合。当打开文件进行读写时,它变成一个 流。 从根本上说,流是通过通信路径传递的字节序列。有两个主要的流:输入流 和 输出流。输入流用于从文件读取数据(读操作),输出流用于向文件写入数…...
Ubuntu18.04 python 开发usb通信
一、安装环境 1.安装pip sudo python3 get-pip.py 或 sudo -i apt update apt install python3-pip 确定pip是否安装成功: xxx-desktop:~$ pip3 --versionpip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)2.安装pyusb pip3 install pyusb --use…...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...

Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...