全局异常 @ControllerAdvice 该怎么写

本文首发于稀土掘金:全局异常 @ControllerAdvice 该怎么写,该账号即为本人账号,非搬运。

问题由来

很多小伙伴刚进公司做项目的时候,会看到项目里面有一个@ControllerAdvice标记的类,整个类的编码结构大概是这样子:

@Slf4j
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {@ExceptionHandler(MyException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public MyExceptionResponse myException(MyException e) {//记录日志log.error("自定义的异常:" + e.getMessage());//其他处理逻辑。。。。//返回结果MyExceptionResponse response = new MyExceptionResponse(1001, "自定义的异常:" + e.getMessage());return response;}@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public MyExceptionResponse exception(Exception e) {//记录日志log.error("exception异常:" + e.getMessage());//其他处理逻辑。。。。//返回结果MyExceptionResponse response = new MyExceptionResponse(1002, "exception异常:" + e.getMessage());return response;}
}

其实你随便在网上搜一下@ControllerAdvice或者@ExceptionHandler,就知道这个是统一的全局异常处理,不过你有没有想过,我们为什么需要统一的全局异常处理,以及他的编码结构为啥发展成了上面的样子。

为什么要处理异常

为什么需要处理异常,因为异常是不可避免的,程序总是可能发生异常,所以我们需要处理异常,如果你就是不编写处理异常的代码,程序里面发生的异常最终会抛给spring,而spring其实是提供了异常处理的,也就是你不处理异常,异常就由spring来处理,那么spring处理异常的结果就是,你会在浏览器看到如下界面:

如果你点开F12去看,会看到返回的响应是这样的:

其中Content-Type是text/html,说明spring确实给你返回了一个html界面。

如果你是使用postman、idea的http client这种测试工具,会得到类似这样的结果:

这两种返回结果都是存在的,spring会根据客户端类型来判断是返回html界面还是返回一个json对象。

然而,这两种结果给到前端都是不合适的,一方面是用户体验不好,用户看不懂。另一方面即使用户给你反馈这样的结果,作为程序员的你也看不出来哪里出错了。这里说明一下,系统抛出的异常(包括RuntimeException甚至Exception),其实是带有具体信息的,只是这些异常抛给spring后,spring没有返回具体内容,而是返回上面这些东西,也就是spring把具体信息给隐藏了,只返回了一些静态的默认内容。

因为spring默认返回的结果不够好,所以我们要自定义处理异常的逻辑, 而代码的调用顺序是@Controller->@Service->@Mapper,如果想要捕获所有的异常,就得在最外层进行捕获,也就是在@Controller进行捕获,于是你会写出类似这样的代码:

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/{id}")public ResponseEntity queryById(@PathVariable("id") Long id) {try {//执行业务逻辑User user = userService.queryById(id);//返回结果给前端HttpStatus httpStatus = HttpStatus.OK;ResponseEntity<User> responseEntity = new ResponseEntity<>(user, httpStatus);return responseEntity;} catch (Exception e) {String msg = "查询用户信息异常,id:" + id;//记录日志log.error(msg);//返回异常信息给前端HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;ResponseEntity<String> responseEntity = new ResponseEntity<>(msg, httpStatus);return responseEntity;}}@PostMapping("/add")public ResponseEntity addUser(@RequestBody User user) {try {//执行业务逻辑User user = userService.addUser(user);//返回结果给前端HttpStatus httpStatus = HttpStatus.OK;ResponseEntity<User> responseEntity = new ResponseEntity<>(user, httpStatus);return responseEntity;} catch (Exception e) {String msg = "新增用户信息异常,用户信息:" + JSON.toJSONString(user);//记录日志log.error(msg);//返回异常信息给前端HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;ResponseEntity<String> responseEntity = new ResponseEntity<>(msg, httpStatus);return responseEntity;}}
}

仔细琢磨一下你会发现这种写法非常难受,首先你需要在每一个@Controller类的每一个方法里面都写上try{}catch(){}块,这种重复性就不是我们能接受的。其次执行正常和异常的返回数据结构是不一样的,执行正常的时候一般返回当前业务对应的JavaBean,比如上面例子的User,执行异常的时候不可能返回User,取而代之的是要想办法把异常信息返回给前端,这就导致方法的返回值得定义成ResponseEntity,然后你每次都要去装填ResponseEntity对象。

为了避免如此麻烦的写法,自spring3.2开始,给我们提供了@ControllerAdvice注解来进行统一的异常处理,他的执行效果是这样的:

如果你使用@ControllerAdvice标记了一个类,那么在程序里发生的所有异常,spring都会将他传给@ControllerAdvice标记的那个类里面,让你自己处理异常。该注解基本用法如下:

@Slf4j
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {@ExceptionHandler(RuntimeException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public String runtimeException(RuntimeException e) {//记录日志log.error("runtime异常:" + e.getMessage());//返回结果return "runtime异常:" + e.getMessage();}@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public String exception(Exception e) {//记录日志log.error("exception异常:" + e.getMessage());//返回结果return "exception异常:" + e.getMessage();}
}

说明:

  1. 首先你不需要在每一个@Controller的每一个@RequestMapping方法里都写try{}catch(){}代码块了,spring会将发生的异常传递到@ControllerAdvice类里面的@ExceptionHandler方法,具体传递到哪个方法,是由异常的类型来进行划分的,也就是同一类型的异常执行相同的处理逻辑,像上面的例子就是将Exception下面的RutimeException做单独处理,其余的都归到Exception来处理。
  2. 其次你的@Controller里的@RequestMapping方法的返回值类型不再需要写ResponseEntity了,可以写具体业务对应的JavaBean,因为发生异常时的返回值类型由@ExceptionHandler所在方法的返回值定义了。
  3. @ControllerAdvice的用法和@Controller有点像,比如都可以在类上面添加@ResponseBody,让类里面的方法可以返回字符串或直接返回JavaBean。
  4. 最后说下@ResponseStatus,这个注解是用来设置http响应码的,这里我设置的“HttpStatus.INTERNAL_SERVER_ERROR”会返回500给前端,如果你不设置的话,会返回默认值200。

以上是spring提供的统一异常处理@ControllerAdvice的基本用法,不过他和文章开头的代码还是有些区别,我们来看看这段代码还有什么可以优化一下。

自定义业务异常类

就异常的来源来说,除了系统抛出的异常,其实我们自己也可以抛出异常,比如在业务执行过程中发现不对的时候,会通过抛异常的方式让程序终止:

@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {User user = userService.queryById(id);if(user == null){throw new RuntimeException("该用户不存在");}//可能还有其他的业务逻辑。。。。//最终返回结果return user;
}

其实这样子抛出的异常,也可以被上面“基本用法”当中的@ControllerAdvice接收和处理,但是这种异常情况,其实是在我们的预期范围内的,是在我们的预期下主动抛出的,是一种“业务异常”,他和系统抛出的RuntimeException不一样,像空指针、数组越界之类的(他们都是RuntimeException的子类)他们表示你代码写的有问题,代码不够完善,而我们自己抛出的异常,是一种在预期范围内的业务逻辑上的错误,我们希望把他们给区分开来,于是会为这种业务异常单独定义一个类:

public class MyException extends RuntimeException {private String errorMsg;public MyException() {}//省略getter、setter等方法
}

然后在抛异常的时候抛出自定义类的异常:

@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {User user = userService.queryById(id);if(user == null){throw new MyException("该用户不存在");}//可能还有其他的业务逻辑。。。。//最终返回结果return user;
}

于是@ControllerAdvice里面的写法会变成这样:

@Slf4j
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {//主要改动点在这里,把RuntimeException.class改成了MyException.class@ExceptionHandler(MyException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public String myException(MyException e) {//记录日志log.error("自定义的异常:" + e.getMessage());//返回结果return "自定义的异常:" + e.getMessage();}@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public String exception(Exception e) {//记录日志log.error("exception异常:" + e.getMessage());//返回结果return "exception异常:" + e.getMessage();}
}

其实就是把原来的RuntimeException改成了自定义的异常,另外仍然由Exception处理自定义异常之外的所有异常。

统一返回给前端的数据结构

接下来的一个优化就是,统一返回给前端的数据结构,上面代码当中返回给前端的都是一个字符串,用来描述异常信息,但其实更好的做法是像@Controller里的接口那样,返回一个JavaBean,甚至是所有类型的异常都返回一个格式统一的JavaBean,于是我们会定义一个用于表示返回值数据结构的类:

public class MyExceptionResponse implements Serializable {//其实这个code不是必须的,//不过下面的errorMsg就比较有必要,你总得有一个字符串来描述你的异常信息吧private int code;private String errorMsg;public MyExceptionResponse() {}//其他的什么构造方法、getter、setter方法就不写了,大家能看明白就行
}

最终得到的@ControllerAdvice就变成下面这样:

@Slf4j
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {@ExceptionHandler(MyException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public MyExceptionResponse runtimeException(MyException e) {//记录日志log.error("自定义的异常:" + e.getMessage());//其他处理逻辑。。。。//返回结果MyExceptionResponse response = new MyExceptionResponse(1001, "自定义的异常:" + e.getMessage());return response;}@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public MyExceptionResponse exception(Exception e) {//记录日志log.error("exception异常:" + e.getMessage());//其他处理逻辑。。。。//返回结果MyExceptionResponse response = new MyExceptionResponse(1002, "exception异常:" + e.getMessage());return response;}
}

其他补充

注意一下,@ControllerAdvice是对@Controller进行功能增强,搭配@ExceptionHandler进行全局异常处理只是他其中的一个用法(可以认为异常处理也是一种功能增强),他还可以搭配@InitBinder或者@ModelAttribute实现其他功能,基本都是对@Controller进行增强。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序员极力推荐的几款插件

前言 h之前分享程序员喜欢的神器之后&#xff0c;还是相当多的伙伴喜欢&#xff0c;那一期的工具不仅是协助你工作还是工作外摸鱼划水&#xff0c;体验感是相当不错的。 这一期我还是打算把我非常喜欢用的几款插件分享给大家&#xff0c;这几款差劲不仅帮助我节省很多时间&am…

2024年软件系统与信息处理国际会议(ICSSIP 2024)即将召开!

2024年软件系统与信息处理国际会议&#xff08;ICSSIP 2024&#xff09;将于2024年10月25-27日在中国昆明举行。引领技术前沿&#xff0c;共谋创新未来。ICSSIP 2024将汇聚来自世界各地的专家学者&#xff0c;他们将在会上分享最新的研究成果、技术突破及实践经验。会议议题涵盖…

WAAP替代传统WAF已成趋势

数字化时代&#xff0c;Web应用和API已成为企业运营的核心。然而&#xff0c;随着网络攻击手段的不断进化&#xff0c;自动化攻击愈发频繁&#xff0c;传统的Web应用防火墙&#xff08;WAF&#xff09;已难以满足现代企业的安全需求。WAAP&#xff08;Web Application and API …

我出一道面试题,看看你能拿 3k 还是 30k!

大家好&#xff0c;我是程序员鱼皮。欢迎屏幕前的各位来到今天的模拟面试现场&#xff0c;接下来我会出一道经典的后端面试题&#xff0c;你只需要进行 4 个简单的选择&#xff0c;就能判断出来你的水平是新手&#xff08;3k&#xff09;、初级&#xff08;10k&#xff09;、中…

大镜山阿里巴巴国际站数据采集软件使用方法|阿里国际站商家信息采集软件使用方法|阿里国际站信息采集软件使用方法

大镜山阿里巴巴国际站数据采集软件一款采集阿里巴巴国际站alibaba.com商家数据的软件&#xff0c;采集的数据包括店铺名称、店铺年份、评分、邮件地址、手机号码、网址及社交连接等。 下载大镜山阿里巴巴国际站数据采集软件 大镜山阿里巴巴国际站数据采集软件下载地址 大镜山…

ubuntu20.04.6 安装Skywalking 10.0.1

1.前置准备 1.1. **jdk17&#xff08;Skywalking10 jdk22不兼容&#xff0c;用17版本即可&#xff09;**安装&#xff1a; https://blog.csdn.net/CsethCRM/article/details/140768670 1.2. elasticsearch安装&#xff1a; https://blog.csdn.net/CsethCRM/article/details…

Java-21推崇的虚拟线程到底有好快?一起来看这个实验

我们一起来看下面这个场景&#xff1a;十万个待执行任务&#xff0c;每个任务休眠两秒 1. 采用java-21的虚拟线程池来实现 public static void main(String[] args) throws InterruptedException{ExecutorService VIRTUAL_THREAD_POOL Executors.newThreadPerTaskExecutor(Th…

爬虫程序在采集亚马逊站点数据时如何绕过验证码限制?

引言 在电商数据分析中&#xff0c;爬虫技术的应用日益广泛。通过爬虫技术&#xff0c;我们可以高效地获取大量的电商平台数据&#xff0c;这些数据对于市场分析、竞争情报、价格监控等有着极其重要的意义。亚马逊作为全球最大的电商平台之一&#xff0c;是数据采集的重要目标…

pdf文件损坏打不开怎么修复?文档损坏原因和修复办法分享!

pdf是一种优点很多的软件&#xff0c;它在文件传输过程中格式不会乱掉&#xff0c;而且还可以加密&#xff0c;特别的方便。pdf这种文件格式&#xff0c;不仅能呈现文档&#xff0c;还可以呈现图像&#xff0c;工作中经常会用到。 不过&#xff0c;因为种种原因&#xff0c;有…

【iOS】——Block底层实现和捕获机制

Block的实质 Block的定义是带有自动变量的匿名函数&#xff0c;下面从源码的角度探究下Block究竟是什么 下面是一个Block的简单实现&#xff1a; int main(int argc, const char * argv[]) {autoreleasepool {// insert code here...void (^blk)(void) ^{printf("Bloc…

WordPress原创插件:搜索引擎抓取首图seo图片

WordPress原创插件&#xff1a;搜索引擎抓取首图seo图片 插件设置 插件将在网站头部添加适当的meta标签&#xff0c;以便百度等搜索引擎抓取指定的固定图像。 插件下载 https://download.csdn.net/download/huayula/89596527

Docker容器数据库启动,如何用别名JAR jdbc:postgresql://别名:5432/postgres

如果想了解为啥这样做得同学&#xff0c;请去看这个文章 Docker容器网络&#xff08;七&#xff09;_host.docker.internal-CSDN博客 因为docker0网络&#xff0c;需要用别名的话&#xff0c;还得在host文件加 dockerIp(172.0.0.2) 别名 怎么查&#xff0c; docker network …

C语言:扫雷游戏实现

一、扫雷游戏的分析和设计 扫雷游戏想必大家都玩过吧&#xff0c;初级的玩法是在一个9*9的棋盘上找到没有雷的格子&#xff0c;而今天我们就要做的就是9*9扫雷游戏的实现。 1、游戏功能和规则 使用控制台实现经典的扫雷游戏游戏可以通过菜单实现继续玩或者退出游戏扫雷的棋盘…

嵌入式学习第11天——C语言选择结构

2024年7月29日 第11天 选择&#xff08;分支&#xff09;结构 分支结构&#xff1a;又被称为选择结构 概念 选择结构&#xff1a;根据条件成立与否&#xff0c;选择相应的操作。 条件构建 关系表达式&#xff1a;含有关系运算符的表达式&#xff08;>,<,>,<,!…

贪心系列专题篇三

目录 单调递增的数字 坏了的计算器 合并区间 无重叠区间 用最少数量的箭 声明&#xff1a;接下来主要使用贪心法来解决问题&#xff01;&#xff01;&#xff01; 单调递增的数字 题目 思路 如果我们遍历整个数组&#xff0c;然后对每个数k从[k,0]依次遍历寻找“单调递…

【计算机毕设论文】基于SpringBoot的诗词管理系统

&#x1f497;博主介绍&#xff1a;✌全平台粉丝5W,高级大厂开发程序员&#x1f603;&#xff0c;博客之星、掘金/知乎/华为云/阿里云等平台优质作者。 【源码获取】小伙伴可以关注我 感兴趣的可以先收藏起来&#xff0c;同学门有不懂的毕设选题&#xff0c;项目以及论文编写等…

一款DC双向马达驱动电路的桥式驱动芯片 - SS6286L

电机驱动芯片 - SS6286L是一款DC双向马达驱动电路&#xff0c;它适用玩具类别的电机驱动、自动阀门电机驱动、电磁门锁驱动等。它有两个逻辑输入端子用来控制电机前进、后退及制动。该电路具有良好的抗干扰性&#xff0c;微小的待机电流、低的输出内阻&#xff0c;同时&#xf…

Could not install packages due to an EnvironmentError: [WinError 5]

Could not install packages due to an EnvironmentError: [WinError 5] 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城…

通配符https证书的申请途径和配置方法

一、通配符SSL证书的功能 通配符SSL证书&#xff0c;也被称为泛域名证书&#xff0c;是一种特殊类型的SSL证书&#xff0c;它能够保护一个主域名及其所有次级子域名&#xff08;不可跨级保护&#xff09;。例如&#xff0c;如果您的主域名是example.com&#xff0c;那么一个通…

四款2024年不入耳耳机排行榜分享,入耳式耳机戴不舒服的可入手

随着长时间的入耳式耳机佩戴&#xff0c;很多人都发现其实入耳式耳机逐步成为了引起耳朵感染疾病的原因之一。因为经常佩戴入耳式耳机会导致耳道湿度高、微生物更容易生长&#xff0c;进而耳道分泌物也更多了&#xff0c;所以其实现在流行的不入耳耳机&#xff0c;也就是开放式…