【Java】J2EE异常处理设计整理

1. 业务异常介绍

我们在处理应用中的异常时,通常可以将应用中所遇到的异常分为两大类,一种是业务异常,一种是非业务异常。

  • 业务异常是指在进行正常的业务处理时,由于某些业务的特殊需求而导致处理不能继续执行所抛出的异常,这种异常是由开发人员所定义的,它属于可以预知的异常。
  • 非业务异常是指在正常情况下所产生的异常。例如,由于网络故障而导致无法访问数据库,必要的配置文件不存在等情况下所产生的异常都属于非业务异常。非业务异常是不可预知的。

对于这两种类型的异常应该采取不同的处理策略。Java提供了非常优秀的异常处理机制:异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。

2 业务异常设计

项目开发过程中,对系统的异常设计方面的要求和规范里面分为两种异常:系统异常和应用异常。前者以RuntimeException的子类实现,后者以Exception的子类实现。

遇到应用级别的非正常情况,即项目中出现需要处理业务异常的情况时,可能需要提示给当前用户或上层API,(比如:用户登录时可能会出现:用户名密码错误、当前用户已经登录、当前用户积分不足不允许再次使用等等情况),这个时候,你需要定义自己的应用异常,并抛出给上层程序(很可能是UI层,他们catch后,根据当时的上下文,形成一个字符串以提示用户)。

另外遇到系统级别的问题,即项目中出现需要处理非业务异常的情况时,(你的应用程序里面处理不了,比如:意外的IOException、SQLExcetpion)应该直接包装成RuntimeException的子类并抛出。

2.1 业务异常处理原则

使用Checked Exception还是UnChecked Exception的原则,需要根据实际项目的开发需求和式样中的业务要求而定。 如果是项目自身的业务逻辑流程问题,希望强制你的类调用者来处理异常,那么就用Checked Exception;反之如果是系统自身的问题,你不希望强制你的类调用者来处理异常,就用UnChecked Exception。

以用户登陆的例子来说,可能产生的异常有:

  • IOException (例如读取配置文件找不到)
  • SQLException (例如连接数据库错误)
  • ClassNotFoundException(找不到数据库驱动类)
  • NoSuchUserException
  • PasswordNotMatchException

以上3个异常是和业务逻辑无关的系统容错异常,所以应该转换为RuntimeException,不强制类调用者来处理;而下面两个异常是和业务逻辑相关的流程,从业务实现的角度来说,类调用者必须处理,所以要Checked,强迫调用者去处理。 至于类调用者catch到NoSuchUserException和PasswordNotMatchException怎么处理,也要根据实际项目的开发需求和式样中的业务要求中的具体的业务逻辑了。或者他有能力也应该处理,就自己处理掉了;或者他不关心这个异常,也不希望上面的类调用者关心,就转化为RuntimeException;或者他希望上面的类调用者处理,而不是自己处理,就转化为本层的异常继续往上抛出来。

通常情况下导致业务异常的原因是由于操作人员的人为因素所导致,属于人为原因。例如,操作不当,执行业务的条件不满足等都属于人为原因,也就是可以通过操作员的处理来规避这些异常的产生,从而正确完成业务的处理。

业务异常的产生通常是在业务处理过程中,根据业务处理的状态和条件,由开发人员编码抛出的。具体是指正常的业务处理过程中,由于某些业务的特殊要求或者项目式样本身的需求而导致处理不能继续所抛出的异常。

所以,针对业务异常处理的原则通常是:在业务层或者业务处理方法中抛出异常,在表示层拦截异常,并将异常以友好的方式反馈给操作者,以便其可以依据提示信息正确地完成业务功能的处理。在这里要注意的是,在表示层拦截异常信息也不是只需要针对每个异常都进行拦截和处理,而是要充分利用框架来进行异常的统一处理。最好做到正常的处理流程中看不到任何异常的处理。

2.2 业务异常分层设计

对于异常系统的结构通常会被划分为三个层次。第一层:异常基类,第二层:功能层或者模块层,第三层:业务异常层,层与层之间是父子式的继承关系。

对于一个通用的异常系统而言,通常会定义一个异常的基类,假设是BaseException,该类继承自RuntimeException或者ApplicationException。这样对于业务异常就可以在运行时由系统的框架进行捕获。

接着在BaseException的基础上,为应用中的每个层次定义一个异常基类,例如,业务层的异常可以定义为BusinessException,持久层的异常可以定义为DAOException等。当然,这一层的异常也可以按照功能或者模块来进行划分,划分的方式主要依赖于顶层对异常的处理方法。

最后为每个业务异常定义相应的一个业务对象。另外为了减少异常对象的数量,在这一层也可以采用错误代码的设计方式,也就是为每个业务处理异常定义一个系统唯一的错误代码,使得顶层的拦截程序可以依据错误代码来得到相应的错误信息。

2.3 业务异常处理方法

Web系统通常可分为三层:表示层,业务层和数据层。清晰的异常分层将为异常处理机制提供一致性的平台,同时也能加强程序可服用性和可维护性。一般我们通常将异常设计为以下4大类:

  • ApplicationException:业务逻辑产生的错误,带有详细错误描述信息以便反馈给更高一层或用户。
  • SystemException:可侦测的系统故障,但并不期望发生。如网络故障,数据库连接失败。
  • RuntimeException:潜在的软件错误(bug)。入访问一个未被初始化的对象。
  • AssertionException:判断过程中产生的错误,不被期望发生。

根据此分类,各层中可能产生的异常为:

  • 表示层:(无)
  • 业务层
    • 服务层:ApplicationException, SystemException
    • 业务逻辑层:ApplicationException, RuntimeException, AssertionException
    • 数据访问层: SystemException, RuntimeException, AssertionException
  • 数据层:SystemException

因此最终可以在代码的更高一层对异常统一处理,这样就避免了异常的“过早”处理,也体现了“思维分离”的编程思想。

2.4 业务异常处理目标

一个成功的系统异常处理结构一定要尽量达到下面的目标:

  • 减少代码的复杂性
  • 捕获和保存诊断性信息
  • 对合适的人提醒注意
  • 优雅地退出行动

系统异常是项目应用的真实意图的干扰,通常和应用逻辑无关,而是底层系统出现的问题,如数据库服务器异常终止,网络连接中断或者应用软件自身存在缺陷。终端用户不能修复这种错误,而需要通知系统管理员或者软件开发人员来处理。因此,用来处理它的代码应尽量的少,理想上,把系统异常和业务异常部分隔离开。系统异常的处理必须满足那些负责改正它们的人的需要。而对于项目开发人员只需要知道故障发生了,并得到能帮助他们搞清为何发生的异常信息。即使一个系统异常,虽然在定义上而言,是不可补救的,但是好的系统异常处理会试着优雅地结束引起故障的活动。

系统异常需要反馈给管理员和项目开发人员,首先需要在适当的地方捕捉它们并执行相应的系统异常处理单元。理论上我们可以在系统异常出现的任何地方实时处理,比如数据层,但是处理系统异常的一个重要原则就是系统应用尽可能最大程度恢复到异常出现之前的状态,只有这样才可以保证数据的完整性并让管理员或项目开发人员有机会纠正错误。

因此对于像在数据层出现的SQLException异常,因为它可能是当前事务的一部分,不可独立处理,仅仅需要做的只是抛出它们,而直到业务层才捕捉并回滚当前事务。实际开发中,异常处理需要定义一个清晰的异常层次结构,并避免在底层中直接处理异常,尽量将它们在更高层集中处理。即遵循“Throw Earlier, Catch Later”原则。

从开发应用的角度来看,可以把异常分为系统异常和应用异常。系统异常在性质上比应用异常更加严重,前者通常和应用逻辑无关,而是底层系统出现了问题,如数据库服务器异常终止,网络连接中断或者应用软件自身存在缺陷。终端用户不能修复这种错误,而需要通知系统管理员或者软件开发人员来处理。

应用异常是由于违反了商业规则或者业务逻辑而导致的错误。例如,一个被锁定的用户试图登入应用。这种错误不是致命的错误,可以把错误信息报告给用户,让用户进行相应的处理操作。

在Web项目中,针对不同的异常,可以采取不同的措施。如果是用户能够恢复的应用异常,那么可以把控制流程转回到用户输入页面,向用户友好地汇报所出现的问题以及应该采取的恢复措施。如果是系统异常,可以显示一个系统出错页面,建议用户让系统管理员来解决这个问题。

3 常见API异常

3.1 java.lang.NullPointerException

解释:“程序遇上了空指针”,简单地说就是调用了未经初始化的对象或者是不存在的对象,这个错误经常出现在创建图片,调用数组这些操作中,比如图片未经初始化,或者图片创建时的路径错误等等。对数组操作中出现空指针,很多情况下是一些刚开始学习编程的朋友常犯的错误,即把数组的初始化和数组元素的初始化混淆起来了。数组的初始化是对数组分配需要的空间,而初始化后的数组,其中的元素并没有实例化,依然是空的,所以如果要调用对象的话,还需要对每个元素都进行初始化操作。其实简单的说,就是在代码中当应用试图在要求使用对象的地方使用了null时,抛出该异常。

3.2 java.lang.ClassNotFoundException

解释:“指定的类不存在”, 该异常是指在程序中找不到要访问或者需要调用的类时出现的异常。当应用试图根据字符串形式的类名来构造一个实体类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。这里主要考虑一下类的名称和路径是否正确即可。

3.3 java.lang.ArithmeticException

解释:“数学运算异常”,比如程序中出现了除以零这样的运算就会出这样的异常,对这种异常,大家就要好好检查一下自己程序中涉及到数学运算的地方,公式是不是有不妥了。算术条件异常很容易发生。比如:整数除零等。

3.4 java.lang.ArrayIndexOutOfBoundsException

解释:“数组下标越界”,现在程序中大多都有对数组的操作,因此在调用数组的时候一定要认真检查,看自己调用的下标是不是超出了数组的范围,一般来说,显示(即直接用常数当下标)调用不太容易出这样的错,但隐式(即用变量表示下标)调用就经常出错了,还有一种情况,是程序中定义的数组的长度是通过某些特定方法决定的,不是事先声明的,这个时候,最好先查看一下数组的length,以免出现这个异常。对于数组索引越界异常,其实具体的应用中就是当对数组的索引值为负数或大于等于数组大小时抛出该异常。

3.5 java.lang.IllegalArgumentException

解释:“方法的参数错误”,很多类库中的方法在一些情况下都会引发这样的错误,因此一旦发现这个异常,我们要做的,就是赶紧去检查一下方法调用中的参数传递是不是出现了错误。

3.6 java.lang.IllegalAccessException

解释:“没有访问权限”,当应用程序要调用一个类,但当前的方法即没有对该类的访问权限便会出现这个异常。对程序中用了package的情况下要注意这个异常。对于违法的访问异常。通常程序应用试图通过反射方式创建某个类的实例、访问该类属性、调用该类方法,而当时又无法访问类的、属性的、方法的或构造方法的定义时抛出该异常。

3.7 java.lang.ArrayStoreException

解释:“数组存储异常”。当向数组中存放非数组声明类型对象时抛出。

3.8 java.lang.ClassCastException

解释:“类转型异常”。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。

3.9 java.lang.CloneNotSupportedException

解释:“不支持克隆异常”。当没有实现Cloneable接口或者不支持克隆方法时,调用其clone()方法则抛出该异常。

3.10 java.lang.EnumConstantNotPresentException

解释:“枚举常量不存在异常”。当应用试图通过名称和枚举类型访问一个枚举对象,但该枚举对象并不包含常量时,抛出该异常。

3.11 java.lang.Exception

解释:“根异常”。用以描述应用程序希望捕获的情况。

3.12 java.lang.IllegalMonitorStateException

解释:“违法的监控状态异常”。当某个线程试图等待一个自己并不拥有的对象(O)的监控器或者通知其他线程等待该对象(O)的监控器时,抛出该异常。

3.13 java.lang.IllegalStateException

解释:“违法的状态异常”。当在Java环境和应用尚未处于某个方法的合法调用状态,而调用了该方法时,抛出该异常。

3.14 java.lang.IllegalThreadStateException

解释:“违法的线程状态异常”。当线程尚未处于某个方法的合法调用状态,而调用了该方法时,抛出异常。

3.15 java.lang.IndexOutOfBoundsException

解释:“索引越界异常”。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。

3.16 java.lang.InstantiationException

解释:“实例化异常”。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。

3.17 java.lang.InterruptedException

解释:“被中止异常”。当某个线程处于长时间的等待、休眠或其他暂停状态,而此时其他的线程通过Thread的interrupt方法终止该线程时抛出该异常。如果一个抽象类实例化时产生的异常事件将该线程停止执行若干时间,此时必须用try/catch异常处理程序进行相应的异常处理。

3.18 java.lang.NegativeArraySizeException

解释:“数组大小为负值异常”。当使用负数大小值创建数组时抛出该异常。

3.19 java.lang.NoSuchFieldException

解释:“属性不存在异常”。当访问某个类的不存在的属性时抛出该异常。

3.20 java.lang.NoSuchMethodException

解释:“方法不存在异常”。当访问某个类的不存在的方法时抛出该异常。

3.21 java.lang.NumberFormatException

解释:“数字格式异常”。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。

3.22 java.lang.RuntimeException

解释:“Java运行时异常”。是所有Java虚拟机正常操作期间可以被抛出的异常的父类。

3.23 java.lang.SecurityException

解释:“违背安全原则异常”。由安全管理器抛出,用于指示违反安全情况的异常。

3.24 java.lang.StringIndexOutOfBoundsException

解释:“字符串索引越界异常”。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。

3.25 java.lang.TypeNotPresentException

解释:“类型不存在异常”。当应用试图以某个类型名称的字符串表达方式访问该类型,但是根据给定的名称又找不到该类型是抛出该异常。该异常与ClassNotFoundException的区别在于该异常是unchecked(不被检查)异常,而ClassNotFoundException是checked(被检查)异常。

3.26 java.lang.UnsupportedOperationException

解释:“不支持的方法异常”。指明请求的方法不被支持情况的异常。

3.27 java.lang.EOFException

解释:“文件已结束异常”。常常用来指明判断一个文件的正常结束位置。在程序中,虽然这个异常在程序中出现的可能性不是很大,但是使用这个异常的作用却很大,经常可能用于代码的循环判断中出现。

3.28 java.lang.FileNotFoundException

解释:“文件未找到异常”。 该异常是指找不到对应文件异常。当应用程序试图根据字符串形式的文件名称构造类,但是在遍历XML配置文件之后却找不到对应名称的文件时,会抛出该异常。

3.29 java.lang.SQLException

解释:“操作数据库异常”。 该异常的绝大多数原因都是数据库的配置,连接,或其所在的硬件设施造成的。如果此时数据库服务器还没有运行起来,或者一个未有连上的网络的数据线,访问数据库的密码改变等等,都有可能导致SQLException系统异常的发生。

3.30 java.lang.IOException

解释:“输入输出异常”。 在Java中,任何一个类库方法的调用就可能让该异常发生。IOException异常不仅仅发生在文件读写,还包括很多stream的读写操作中。具体涉及在项目中出现的情况可能是一个文件数据的打印操作,文件数据的CSV出力操作,或者是文件数据的导入导出操作中导致IOException异常的发生。

3.31 java.lang.DataFormatException

解释:“数据格式异常”。当程序中,对Java数据进行操作处理时,如果发现数据的格式错误,例如:数据的时间格式和日期格式错误时,将抛出该异常。

发布了44 篇原创文章 · 获赞 40 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/gavinbj/article/details/104363603