项目开发中异常处理需要注意的问题(详细!!)

news2024/12/31 7:03:11

文章目录

  • 1、各层在对异常处理时需要注意的问题
  • 2、业务代码层面对于异常的处理姿势
  • 3、错误的异常处理方式:
    • 1、丢弃异常
    • 2、丢失异常的原始信息
    • 3、抛出异常时不指定任何信息
  • 4、线程池处理异常方法

1、各层在对异常处理时需要注意的问题

这是日常开发中请求的处理过程:
在这里插入图片描述
针对异常来说:
•Repository(DAO) 层出现异常可以忽略、可以降级、可以转化为一个友好的异常。如果一律捕获异常仅记录日志,很可能业务逻辑已经出
错,而用户和程序本身完全感知不到。

•Service 层往往涉及数据库事务,出现异常同样不适合捕获,否则事务无法自动回滚。此外 Service 层涉及业务逻辑,有些业务逻辑执行中遇到业务异常,可能需要在异常后转入分支业务流程。如果业务异常都被框架捕获了,业务功能就会不正常,执行不到设计好的业务流程。

•如果下层异常上升到 Controller 层还是无法处理的话。Controller 层往往会给予用户友好提示,或是根据每一个 API 的异常表返回指定的异常类型,同样无法对所有异常一视同仁。

2、业务代码层面对于异常的处理姿势

  1. 对于自定义的业务异常,以 Warn 级别的日志记录异常以及当前 URL、执行方法等信息后,提取异常中的错误码和消息等信息,转换为合适的结果返回包装体(ResultUtil),返回给 API 调用方;
  2. 对于无法处理的系统异常,以 Error 级别的日志记录异常和上下文信息(比如 URL、参数、用户 ID)后,转换为普适的“服务器忙,请稍后再试”异常信息,同样以 结果返回包装体返回给调用方。

通常通过定义统一异常拦截来处理业务代码中的异常:

@RestControllerAdvice
@Slf4j
public class RestControllerExceptionHandler {
 	private static int GENERIC_SERVER_ERROR_CODE = 2000;
 	private static String GENERIC_SERVER_ERROR_MESSAGE = "服务器忙,请稍后再试";
 @ExceptionHandler
 	public APIResponse handle(HttpServletRequest req, HandlerMethod method, Exception ex) {
 		if (ex instanceof BusinessException) {
 			BusinessException exception = (BusinessException) ex;
 			log.warn(String.format("访问 %s -> %s 出现业务异常!", req.getRequestURI(), method.toString()), ex);
 			return new APIResponse(false, null, exception.getCode(), exception.getMessage());
 		} else {
 			log.error(String.format("访问 %s -> %s 出现系统异常!", req.getRequestURI(), method.toString()), ex);
 			return new APIResponse(false, null, GENERIC_SERVER_ERROR_CODE, GENERIC_SERVER_ERROR_MESSAGE);
 }
 }
}

注: @ExceptionHandler,作用于方法,声明被注解方法是一个异常处理方法,value属性可以过滤拦截指定异常。 @RestControllerAdvice,作用于类,用于定义一个全局异常处理类,拦截所有restcontroller的异常,返回给前端的数据是json格式。 APIResponse为自定义的结果返回包装体,主要包括code、message、data三个属性

3、错误的异常处理方式:

1、丢弃异常

在任何时候,我们捕获了异常都不应该生吞,也就是直接丢弃异常不记录、不抛出。这样的处理方式还不如不捕获异常,因为被生吞掉的异常一旦导致Bug,就很难在程序中找到蛛丝马迹,使得 Bug 排查工作难上加难。通常情况下,生吞异常的原因,可能是不希望自己的方法抛出受检异常,只是为了把异常“处理掉”而捕获并生吞异常,也可能是想当然地认为异常并不重要或不可能产生。但不管是什么原因,不管是你认为多么不重要的异常,都不应该生吞,哪怕是一个日志也好。

2、丢失异常的原始信息

形如:

private void readFile() throws IOException {
 Files.readAllLines(Paths.get("a_file"));
}
@GetMapping("wrong1")
public void wrong1(){
 	try {
 	readFile();
 	}catch (IOException e) {
 	//只保留了异常消息,栈没有记录
 	log.error("文件读取错误, {}", e.getMessage());
 	throw new RuntimeException("系统忙请稍后再试");
}

我们的日志就成了这个样子,谁能说这个是什么错吗?
[12:57:19.746] [http-nio-45678-exec-1] [ERROR] [.g.t.c.e.d.HandleExceptionController:35 ] - 文件读取错误, a_file

通常建议采用下面的方式记录异常信息:

catch (IOException e) {
 log.error("文件读取错误", e);
 throw new RuntimeException("系统忙请稍后再试");
}
或者
catch (IOException e) {
 throw new RuntimeException("系统忙请稍后再试", e);
}

3、抛出异常时不指定任何信息

形如:

catch (Exception e){
	throw new RuntimeException();
}

报错信息:
[13:25:18.031] [http-nio-45678-exec-3] [ERROR] [c.e.d.RestControllerExceptionHandler:24 ] - 访问 /handleexception/wrong3 -> org……demo1.HandleExceptionController#wrong3(String) 出现系统异常!
java.lang.RuntimeException: null

这里的 null 非常容易引起误解。按照空指针问题排查半天才发现,其实是异常的 message 为空。

4、线程池处理异常方法

如果是在任务内部向外抛出异常:

@GetMapping("execute")
public void execute() throws InterruptedException {
 	String prefix = "test";
 	ExecutorService threadPool = Executors.newFixedThreadPool(1, new ThreadFactoryBuilder().setNameFormat(prefix+"%d").get());
 	//提交10个任务到线程池处理,第5个任务会抛出运行时异常
 	IntStream.rangeClosed(1, 10).forEach(i -> threadPool.execute(() -> {
 		if (i == 5) throw new RuntimeException("error");
 		log.info("I'm done : {}", i);
 	}));
 	threadPool.shutdown();
 	threadPool.awaitTermination(1, TimeUnit.HOURS);
}

日志打印:
[14:33:55.990] [test0] [INFO ] [e.d.ThreadPoolAndExceptionController:26 ] - I’m done : 4
Exception in thread “test0” java.lang.RuntimeException: error
at org.geekbang.time.commonmistakes.exception.demo3.ThreadPoolAndExceptionController.lambda$null$0(ThreadPoolAndExceptionController.java:25)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor $Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
[14:33:55.990] [test1] [INFO ] [e.d.ThreadPoolAndExceptionController:26 ] - I’m done : 6

从线程名的改变可以知道因为异常的抛出老线程退出了,线程池只能重新创建一个线程。如果每个异步任务都以异常结束,那么线程池可能完全起不到线程重用的作用。因为没有手动捕获异常进行处理,ThreadGroup 帮我们进行了未捕获异常的默认处理,向标准错误输出打印了出现异常的线程名称和异常信息。显然,这种没有以统一的错误日志格式记录错误信息打印出来的形式,对生产级代码是不合适的。

默认处理方式:

public void uncaughtException(Thread t, Throwable e) {
   if (parent != null) {
      parent.uncaughtException(t, e);
   } else {
      Thread.UncaughtExceptionHandler ueh =
      Thread.getDefaultUncaughtExceptionHandler();
      if (ueh != null) {
         ueh.uncaughtException(t, e);
      } else if (!(e instanceof ThreadDeath)) {
 		System.err.print("Exception in thread \"" + t.getName() + "\" ");
 		e.printStackTrace(System.err);
   }
  }
 }

以 execute 方法提交到线程池的异步任务,最好在任务内部做好异常处理;设置自定义的异常处理程序作为保底,比如在声明线程池时自定义线程池的未捕获异常处理程序:

new ThreadFactoryBuilder()
 	.setNameFormat(prefix+"%d")
 	.setUncaughtExceptionHandler((thread, throwable)-> log.error("ThreadPool {} got exception", thread, throwable))
 	.get()

通过线程池 ExecutorService 的 execute 方法提交任务到线程池处理,如果出现异常会导致线程退出,控制台输出中可以看到异常信息。那么,把 execute方法改为 submit,线程还会退出吗,异常还能被处理程序捕获到吗?修改代码后重新执行程序可以看到如下日志,说明线程没退出,异常也没记录被生吞了:
[15:44:33.769] [test0] [INFO ] [e.d.ThreadPoolAndExceptionController:47 ] - I’m done : 1
[15:44:33.770] [test0] [INFO ] [e.d.ThreadPoolAndExceptionController:47 ] - I’m done : 2
[15:44:33.770] [test0] [INFO ] [e.d.ThreadPoolAndExceptionController:47 ] - I’m done : 3
[15:44:33.770] [test0] [INFO ] [e.d.ThreadPoolAndExceptionController:47 ] - I’m done : 4
[15:44:33.770] [test0] [INFO ] [e.d.ThreadPoolAndExceptionController:47 ] - I’m done : 6
[15:44:33.770] [test0] [INFO ] [e.d.ThreadPoolAndExceptionController:47 ] - I’m done : 7
[15:44:33.770] [test0] [INFO ] [e.d.ThreadPoolAndExceptionController:47 ] - I’m done : 8
[15:44:33.771] [test0] [INFO ] [e.d.ThreadPoolAndExceptionController:47 ] - I’m done : 9
[15:44:33.771] [test0] [INFO ] [e.d.ThreadPoolAndExceptionController:47 ] - I’m done : 10

查看 FutureTask 源码可以发现,在执行任务出现异常之后,异常存到了一个 outcome 字段中,只有在调用 get 方法获取 FutureTask 结果的时候,才会以ExecutionException 的形式重新抛出异常。

既然是以 submit 方式来提交任务,那么我们应该关心任务的执行结果,否则应该以 execute 来提交任务。修改代码如下:

List<Future> tasks = IntStream.rangeClosed(1, 10).mapToObj(i -> threadPool.submit(() -> {
 	if (i == 5) throw new RuntimeException("error");
 	log.info("I'm done : {}", i);
})).collect(Collectors.toList());
tasks.forEach(task-> {
 	try {
 		task.get();
 	} catch (Exception e) {
 		log.error("Got exception", e);
 	}
  }
);

[15:44:13.543] [http-nio-45678-exec-1] [ERROR] [e.d.ThreadPoolAndExceptionController:69 ] - Got exception
java.util.concurrent.ExecutionException: java.lang.RuntimeException: error

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

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

相关文章

springcloud-alibaba (05)Seata实现分布式事务-个人笔记

前言 本文将介绍如何使用Seata实现分布式事务。将覆盖以下主题&#xff1a; seata下载与安装如何配置和启动Seata服务器如何编写应用程序以使用Seata如何解决常见问题 本文只是我个人seata学习笔记&#xff0c;不是什么学习教程 如果你是一名Java开发人员&#xff0c;那么你…

官方喊你来免费下载 Navicat Premium 16.2 Beta 中文版 | Redis 体验官火热招募中

今天&#xff0c;我们发布了 Navicat 16.2 Beta 中文版&#xff0c;它适用于 Windows、macOS 和 Linux 平台。届时&#xff0c;我们诚邀广大 Redis 用户及爱好者亲测 Beta 版&#xff0c;希望 Redis 新功能将为 Redis 相关工作者&#xff08;应用开发人员、DBA 和数据分析师等&…

【paddlecls】多机多卡-linux(二:环境搭建)

构建并进入 docker 容器后&#xff0c;我们进入下一步&#xff1a; 1. 退出/进入 docker 容器&#xff1a; 在进入 Docker 容器后&#xff0c;可使用组合键 Ctrl P Q 退出当前容器&#xff0c;同时不关闭该容器&#xff1b; 如需再次进入容器&#xff0c;可使用下述命令&am…

微信小程序项目实例——生活记账本

今日推荐&#x1f481;‍♂️ 2023五月天演唱会&#x1f3a4;&#x1f3a4;&#x1f3a4;大家一起冲冲冲&#x1f3c3;‍♂️&#x1f3c3;‍♂️&#x1f3c3;‍♂️ &#x1f52e;&#x1f52e;&#x1f52e;&#x1f52e;&#x1f52e;往期优质项目实例&#x1f52e;&…

蓝牙资讯|Counterpoint发布2023年Q1中国智能手表报告

根据市场调查机构 Counterpoint Research 公布的最新报告&#xff0c;2023 年第 1 季度中国智能手表出货量同比下降 28%&#xff0c;环比下降 16%&#xff0c;达到过去 12 个季度以来的最低水平。 本季度智能手表市场中&#xff0c;华为、苹果和小天才&#xff08;imoo&#…

想要入坑C++?当我拿出菱形虚拟继承,阁下又该如何应对

文章目录 &#x1f490;专栏导读&#x1f490;文章导读&#x1f337;继承的定义方式&#x1f337;继承方式与访问限定符&#x1f337;基类和派生类对象赋值转换&#x1f337;继承中的作用域&#x1f337;派生类的默认成员函数&#x1f337;继承与友元&#x1f337;继承与静态成…

基于 ESP32 通过 SMTP 服务器 来发送电子邮件信息

电子邮件在全球范围内被用作数字通信的重要组成部分。电子邮件主要用于官方通信目的,因为它最方便、成本效益高、保存记录、覆盖全球且环保。电子邮件是一种非常快捷的通信方式,只是您需要稳定的互联网连接。 在这个项目中,我们将使用ESP32开发板发送电子邮件(纯文本和 HTM…

因为计算机中丢失VCRUNTIME140怎么办?为什么会丢失VCRUNTIME140.dll

vcruntime140.dll是一个Windows动态链接库&#xff0c;其主要功能是为C/C编译的程序提供运行时支持。这个库在Microsoft Visual Studio 2015中被引入&#xff0c;其名称中的“140”代表版本号。在我们打开运行软件或者游戏程序的时候&#xff0c;电脑提示因为计算机中丢失VCRUN…

windows下编译roadrunner和作为laravel服务器实践

roadrunner源码地址&#xff1a;https://gitee.com/mirrors/RoadRunner?_fromgitee_search windows下编译roadrunner源码获得rr.exe可执行文件 将rr.exe拷贝到laravel目录下 .rr.yaml配置文件内容&#xff1a; version: 3 server: command: "php vendor/spiral/road…

基于Python+AIML+Tornado的智能聊天机器人(NLP+深度学习)含全部工程源码+语料库 适合个人二次开发

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境Tornado 环境 模块实现1. 前端2. 后端3. 语料库4. 系统测试 其它资料下载 前言 本项目旨在利用AIML技术构建一个聊天机器人&#xff0c;实现用户通过聊天界面与机器人交互的功能。通过提供的工程源代码&#xf…

【业务功能篇07】Mysql 模糊查询

业务场景&#xff1a;我们对不同的业务逻辑进行数据处理时&#xff0c;多数是离不开需要模糊匹配的时候&#xff0c;比如要获取该表某个字段中&#xff0c;含有某个具体的字符内容&#xff0c;过滤出业务想要的数据。 这里介绍有这么几种&#xff1a; 一、MySQL通配符模糊查询(…

3D模型Web轻量化工具,如何监测矿藏开采安全与效率?

随着科技的进步&#xff0c;各个领地都在不断探索和应用新的技术来提高效率和准确性。HOOPS技术作为一种先进的3D可视化和模拟技术&#xff0c;正在采掘和地质科学领域发挥着重要的作用。本文将探讨HOOPS技术在采掘和地质科学中的具体应用&#xff0c;并分析其对这些领域的影响…

Maven高级1-分模块开发与依赖问题

1. 分模块开发与设计 将原始模块按照功能拆分成若干个子模块&#xff0c;方便模块间的相互调用&#xff0c;接口共享&#xff1b; 言简意赅就是把功能模块放出去&#xff0c;然后通过在pom文件中导入坐标找到&#xff1b; 注意拆出来的功能模块需要通过Maven指令安装模块选择in…

Python Web后端面试常考数据结构与算法(珍藏版)

本文将对Python web后端面试时常考数据结构与算法进行总结&#xff0c;适合即将找工作或面试的你。Python web后端常考数据结构包括: 常见的数据结构链表、队列、栈、二叉树、堆 使用内置的结构实现高级数据结构&#xff0c;比如内置的list/deque实现栈 LeetCode或者剑指Offe…

【配电网重构】高比例清洁能源接入下计及需求响应的配电网重构【IEEE33节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

循环冗余计算

题目&#xff1a;若信息码字为111000110&#xff0c;生成多项式 x 5 x^5 x5 x 3 x^3 x3 x x x1&#xff0c;则计算crc校验码为()。 将生成多项式的系数作为除数&#xff08;101011&#xff09;&#xff1b; 获得方法1x50x41x30x21x1生成多项式的最高幂次数&#xff08;5&#…

怎样使用TikTok增加销售额?TikTok选品小tips?

哈喽everybody&#xff01;我又来给大家分享干货了&#xff01;今天为大家带来使用TikTok增加销售额和TikTok选品的小tips&#xff0c;让你运营TikTok Shop不迷茫&#xff0c;快快往下看吧&#xff01; 一、如何使用TikTok吸引客户、增加销售额 1.优化产品目录 与任何线上商店…

Python实现KNN算法(附源码)

本篇我们将讨论一种广泛使用的分类技术&#xff0c;称为k邻近算法&#xff0c;或者说K最近邻(KNN&#xff0c;k-Nearest Neighbor)。所谓K最近邻&#xff0c;是k个最近的邻居的意思&#xff0c;即每个样本都可以用它最接近的k个邻居来代表。 01、KNN算法思想 如果一个样本在特征…

绿色智慧档案顺丰环境一体化平台选型表

盛世宏博八防一体化监控系统选型表 序号 功能选择 1 恒温恒湿系统 温湿度监测 口Y&#xff1a;需要 口N&#xff1a;不需要 空调控制 口Y&#xff1a;需要 口N&#xff1a;不需要 加湿机控制 口Y&#xff1a;需要 口N&#xff1a;不需要 除湿…

KD05丨动量RSI策略

大家好&#xff0c;今天我们来分享魔改RSI策略&#xff0c;RSI即相对强弱指数&#xff0c;本质上就是一个动量指标&#xff0c;用于衡量一定时间内价格变动的速度及其变动的大小。它在0-100的范围内变动&#xff0c;通常以70和30作为过热和过冷的界限。要将RSI指标改为一个趋势…