12 | 异常处理:别让自己在出问题的时候变为瞎子
该思维导图由 AI 生成,仅供参考
捕获和处理异常容易犯的错
- 深入了解
- 翻译
- 解释
- 总结
异常处理在软件开发中至关重要。本文介绍了在异常处理过程中容易犯的一些错误以及最佳实践。作者指出了一些常见的错误,比如仅在框架层面粗犷捕获和处理异常、捕获了异常后直接生吞以及丢弃异常的原始信息等。针对这些错误,作者提出了一些最佳实践,比如不建议在框架层面进行异常的自动、统一处理,捕获异常后应该进行合理的处理而不是直接生吞,以及在抛出异常时应该指定明确的异常消息等。文章通过具体的代码示例和分析,帮助读者更好地理解异常处理的重要性以及如何避免常见的错误。异常处理是软件开发中不可或缺的一部分,通过本文的总结,读者可以快速了解异常处理的关键要点,提高自己在异常处理方面的技能水平。文章还提到了一些具体的技术细节,如在finally代码块中的异常处理、避免把异常定义为静态变量等。这些内容对于读者快速了解异常处理的关键技术点具有很大帮助。
《Java 业务开发常见错误 100 例》,新⼈⾸单¥59
全部留言(36)
- 最新
- 精选
- Darren这篇文章收获很大,因为我们现在的系统就是用的统一异常处理,使用的就是老师提到的兜底异常,就是简单的分为业务异常和非业务异常,提示语不同而已。 试着回答下问题: 第一个问题: 肯定是以finally语句块为准。 原因:首先需要明白的是在编译生成的字节码中,每个方法都附带一个异常表。异常表中的每一个条目代表一个异常处理器,并且由 from 指针、to 指针、target 指针以及所捕获的异常类型构成。这些指针的值是字节码索引(bytecode index,bci),用以定位字节码。其中,from 指针和 to 指针标示了该异常处理器所监控的范围,例如 try 代码块所覆盖的范围。target 指针则指向异常处理器的起始位置,例如 catch 代码块的起始位置; 当程序触发异常时,Java 虚拟机会从上至下遍历异常表中的所有条目。当触发异常的字节码的索引值在某个异常表条目的监控范围内,Java 虚拟机会判断所抛出的异常和该条目想要捕获的异常是否匹配。如果匹配,Java 虚拟机会将控制流转移至该条目 target 指针指向的字节码。如果遍历完所有异常表条目,Java 虚拟机仍未匹配到异常处理器,那么它会弹出当前方法对应的 Java 栈帧,并且在调用者(caller)中重复上述操作。在最坏情况下,Java 虚拟机需要遍历当前线程 Java 栈上所有方法的异常表。所以异常操作是一个非常耗费性能的操作; finally 代码块的原理是复制 finally 代码块的内容,分别放在 try-catch 代码块所有正常执行路径以及异常执行路径的出口中。所以不管是是正常还是异常执行,finally都是最后执行的,所以肯定是finally语句块中为准。 第二个问题: IllegalArgumentException:不合法的参数异常,比如说限制不能为空或者有指定的发小范围,调用方没有按照规定传递参数,就可以抛出这个异常; IllegalStateException:如果有状态流转的概念在里面(比如状态机),状态只能从A->B->C,若状态直接从A->C,就可以抛出该异常; UnsupportedOperationException:不支持该操作异常,比如非抽象父类中有个方法,子类必须实现,父类中的方法就可以抛出次异常。老师在集合坑中提到的Arrays.asList 返回的 List 并不是我们期望的 java.util.ArrayList,而是 Arrays 的内部类 ArrayList。ArrayList 内部类继承自 AbstractList 类,并没有覆写父类的 add 方法,而父类中 add 方法的实现,就是抛出 UnsupportedOperationException。
作者回复: 👍🏻,你总结了比较重要的两点,JVM采用异常表控制try-catch的跳转逻辑;对于finally中的代码块其实是复制到try和catch中的return和throw之前的方式来处理的。
2020-04-0516104 - 扎紧绷带老师,为了便于定位异常请求bug,我希望url请求后端出现异常后,框架拦截打印出请求的参数信息,因为body里的参数在流中,只能消费一次,想要在出错后打印,必须要提前复制出一份保存。老师有什么好的实践吗?
作者回复: 这是一个很常见的需求,使用HttpServletRequestWrapper即可,参考 https://github.com/JosephZhu1983/appinfocenter/blob/master/client/src/main/java/me/josephzhu/appinfocenter/client/DumpFilter.java
2020-07-1125 - 梦倚栏杆遇到一个坑(也可以说不理解),和该篇文章没关系,反馈一下 mysql 占位符问题 prepare sqltpl from 'select id,name from table1 where id in (?)'; set @a='1,2,3,4,5,6,7,8,9'; execute sqltpl using @a; 结果:只输出第一条符合条件的记录 prepare sqltpl from 'select name from table1 where name in (?)'; set @a='zhangsan,lisi,wangwu'; execute sqltpl using @a; 结果没有记录
作者回复: 嗯,原因是: 1、id是数字型,传入的1,2,3,4,5,6,7,8,9的并不能转换为数字,所以截断为1(第一个逗号之前的数字) 2、zhangsan,lisi,wangwu整个当做一个字符串来查询了,所以数据库中根本查不到name='zhangsan,lisi,wangwu'这样的记录
2020-04-06318 - 行者老师,关于 千万别把异常定义为静态变量,麻烦分析下为什么cancelOrderRight抛出的异常信息指向createOrderWrong所在的行~
作者回复: 创建异常的时候一次性fillInStackTrace了,除非这样: BusinessException ex = Exceptions.ORDEREXISTS; ex.fillInStackTrace(); throw ex; (这样同样不是线程安全的)
2020-04-04515 - 行者IllegalArgumentException: 入参错误,比如参数类型int输入string。 IllegalStateException: 状态错误,比如订单已经支付完成,二次请求支付接口。 UnsupportedOperationException: 不支持操作错误,比如对一笔不能退款的订单退款。 其他异常 SecurityException: 权限错误,比如未登陆用户调用修改用户信息接口。
作者回复: 不错
2020-04-0414 - 终结者999号关于行者的问题,请老师再解释一下可以吗?貌似没有看懂那个回复,什么叫一次性fillstacktrace?
作者回复: 就是Throwable的stacktrace只是在其new出来的时候才初始化(调用fillInStackTrace方法)是一次性的(除非你手动调用那个方法),而非getStackTrace的时候去获得stacktrace,毕竟我们关心的是异常抛出时的栈。
2020-04-0528 - 小杰老师,看不懂那个异常定义为静态变量的例子,异常打印的是堆栈信息,堆栈信息必须是对象是这个意思吗?
作者回复: 仔细观察一下栈,数据串了,取消订单的异常栈显示了下单的方法
2020-04-053 - yan_12345老师,在业务代码当中,在catch之后,在什么情况下需要重新抛出新的异常,这样做有什么意义和作用?
作者回复: 有意义,把偏向于底层的异常转换为高层异常。换句话说把偏向于内部处理自己才能理解的异常转换为一个外部可以理解的异常抛出。
2020-04-172 - 看不到de颜色好文,目前生产环境就存在出现异常只向上返回e.getMessage(),导致出现预期外的问题时一脸懵逼,这块日后一定要注意。statis Exception这块学习到了,对日后闭坑有很大帮助。通过这篇文章还学到了try-with-resources语法,收货颇丰。
作者回复: 有收货就好。statis->static。
2020-04-111 - skying你好,关于 提交到线程池的线程发生异常的场景。 我本地环境 添加了UncaughtExceptionHandler,但后续的5次循环中,打印的线程还是test1. 本地环境:spring boot2.2.5+undertow
作者回复: 异常处理程序只是作为保底出了异常可以自己来处理,不能防止异常产生。 文中修复方式有 2 步,第一步是比较重要的:以 execute 方法提交到线程池的异步任务,最好在任务内部做好异常处理;设置自定义的异常处理程序作为保底,比如在声明线程池时自定义线程池的未捕获异常处理程序:
2020-04-1041