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…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...
数据库——redis
一、Redis 介绍 1. 概述 Redis(Remote Dictionary Server)是一个开源的、高性能的内存键值数据库系统,具有以下核心特点: 内存存储架构:数据主要存储在内存中,提供微秒级的读写响应 多数据结构支持&…...
2025年- H71-Lc179--39.组合总和(回溯,组合)--Java版
1.题目描述 2.思路 当前的元素可以重复使用。 (1)确定回溯算法函数的参数和返回值(一般是void类型) (2)因为是用递归实现的,所以我们要确定终止条件 (3)单层搜索逻辑 二…...
