目录 start

  1. 异常
    1. 异常的继承关系
    2. 常见异常
    3. 异常常见问题
      1. 应该使用大块的try还是细颗粒度的try?
      2. try和for谁包住谁更好?
    4. 异常的处理
      1. Web应用中全局异常统一处理
      2. Restful应用的全局异常统一处理
      3. 异常和日志的结合
      4. 自定义异常
        1. 自定义异常的错误码

目录 end|2019-10-19 17:04|


异常

相关博客:Java异常浅谈
下面的部分文字内容来源于 JPM的GitChat 群里的交流

异常的继承关系

异常结构
异常结构

Java将所有非正常情况分为两种 异常(Exception)和错误(Error) 它们都继承Throwable父类。
Err错误,一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断,通常应用程序无法处理这些错误;
因此应用程序不应该使用catch块来捕获Error对象,在定义该方法时,也无须在其throws子句中声明该方法可能抛出Error及任何子类。
所以异常处理更多的是指Exception类,而经常处理的一般是RunTimeException 而Exception又分为2种:校验异常运行时异常

非受检异常, 继承于RunTimeException, 其他的都属于受检异常

常见异常

ConcurrentModificationException

  • 常发生于集合并发修改和迭代时

异常常见问题

应该使用大块的try还是细颗粒度的try?

为了避免我们遗漏掉一些可能会出现异常的代码, 所以建议使用大块的try, 因为检查型异常是容易发现的, 但是运行时异常却往往不能在编码的第一时间发现

检验异常在开发中如果不进行处理(捕获处理或声明抛出)编译器就会报错不让通过的, 如果平时没有注意异常的系统性学习,
开发就会有这样一种现象: 程序中通篇只有校验异常. 一般这种校验异常默认的处理方式(使用IDE)是颗粒度小的.
但是程序运行出问题的大多是运行时异常, 空指针, 数组越界, 类型不匹配, 除数为0 等等.
使用大try就不会遗漏运行时异常,但是不能仅仅依靠他, 运行时异常还是尽量使用好的编程习惯来规避的.
当然也是可以在大try中使用小try进行开发这样就能对异常进行具体的捕获和处理以及响应

按下葫芦浮起瓢, 关于异常, 可以从另一个角度: 性能方面的维度去考虑:
异常机制的设计初衷是用于不正常的情形, JVM很少对其生成的字节码进行优化, 把尽可能多的代码放在try块中就会阻止了JVM实行原本可以执行的某些特定优化

try和for谁包住谁更好?

具体业务具体分析, 如果要求循环一出问题后续的循环还是要继续执行, 那么就把try写到for中;
如果要求后续的循环不执行就用try包住for

另外: 使用多线程实现的定时任务在循环处理数据时出现异常必须要及时处理, 否则执行时就会退出


异常的处理

一般来说, 异常都是层层上抛, 中央处理 针对 Service Dao Controller 这种结构的设计, 在Service层进行统一异常处理
除非这个异常是无关大局的, 就是局部发生对其他模块和代码没有影响, 那么就可以就地捕获不需要上抛

不建议使用判断语句进行异常的处理, 这样的维护很费劲, 可读性也不好
大的try块中 一些需要特别说明的就是 特别处理的就要看情况抛出了, 一般进行封装后, 抛出自定义异常, 上层接收后, 进行二次处理和转化,
直到最外层的调用处, 由最外层调用者决定最终如何处理 只要在使用对象前进行对象的非空判断, 基本就能杜绝空指针异常,

  • TODO 这样的话要引入大量的if块, 希望能找到更好的解决方案

Web应用中全局异常统一处理

Restful应用的全局异常统一处理

方法一,nginx上设置拦截,配置对应的跳转页面。
方法二,添加监听器捕获输出异常,然后按自己需要组织返回Json

异常和日志的结合

异常一定要和日志结合使用, 日志记录的简约优雅, 维护才越轻松, 日志的存在就是为了解决问题的方便和有迹可循,
所以要记录的任何信息都是为了解决问题, 无关信息就没有必要放进来了

目前个人用到的就是所在类 方法 行数 时间 报错信息, 根据业务需要还可以加上用户id, 订单id之类的

自定义异常

虽然Java提供了大量的异常类, 但是这些异常类还是难以满足业务的需求, 这个需求可能是用户, 客服, 我们自身

通常自定义异常只要继承Exception 重写相关构造器即可.
一般来说自定义异常具有以下类型: 业务异常, 用户异常, 系统异常, 接口异常, 网络异常, 参数异常等等.
根据项目需要, 可以将异常细分, 比如写一个订单保存, 那么针对订单保存的业务可以在不同的代码逻辑里提示不同的异常信息, 接口级异常也是如此
目的是为了将Exception进行封装, 形成易于理解的异常信息.

自定义异常的设计原则

  1. 用户层面: 用户提示信息要优雅
  2. 系统层面: 让自己维护起来更方便
  3. 接口层面: 查询问题更直观, 轻松, 为自己留证据, 避免推诿扯皮
  4. 网络层面: 及时发现问题, 及时进行处理

自定义异常的错误码

正规项目中都会有接口文档, 也会有规范注释, 在项目中也会有一些静态常量
假设定义一个错误码 00X1 表示空指针, 这个错误码不是给客户或者使用者看的, 而且定义要有规律不能让其他人猜到, 方便查询和管理
规划的越详细, 就会有更为便利的维护方式