目录
异常介绍
java项目中异常设计
异常处理的几种框架
java异常处理
一、异常介绍
异常,为我们处理非正常的业务流程提供了很好的解决方案。异常机制给你的代码可读行、可维护性带来了好处,同时,程序的健壮性也得到了增强。当然,异常也不是万能的,在有些项目中并不适用,比如:纯算法项目,对性能要求极高的项目等(因为异常栈会对系统性能有一定开销,参见:http://www.iteye.com/topic/857722),在这些项目中,最后还得老老实实用方法的返回值去标识方法执行的结果,用if else去处理业务逻辑中的非正常情况吧。
Web应用中对于异常的处理方式与其他形式的应用并没有太大的不同――通过try/catch语句针对不同的异常进行相应处理。但是在具体实现中,由于异常层次、种类繁杂,我们往往很难在Servlet、JSP层妥善的处理好所有异常情况,代码中大量的try/catch代码显得尤为凌乱。
我们通常面对下面两个主要问题:
1. 对异常实现集中式处理
典型情况:对数据库异常记录错误日志。一般处理方法无外两种,一是在各处数据库访问代码的异常处理中,加上日志记录语句。二是将在数据访问代码中将异常向上抛出,并在上层结构中进行集中的日志记录处理。
第一种处理方法失之繁琐、并且导致系统难以维护,假设后继需求为“对于数据库异常,需记录日志,并发送通知消息告知系统管理员”。我们不得不对分散在系统中的各处代码进行整改,工作量庞大。
第二种处理方法实现了统一的异常处理,但如果缺乏设计,往往使得上层异常处理过于复杂。
这里,我们需要的是一个设计清晰、成熟可靠的集中式异常处理方案。
2. 对未捕获异常的处理
对于Unchecked Exception而言,由于代码不强制捕获,往往被程序员所忽略,如果运行期产生了Unchecked Exception,而代码中又没有进行相应的捕获和处理,则我们可能不得不面对尴尬的500服务器内部错误提示页面。
这里,我们需要一个全面而有效的异常处理机制。
二、 java项目中,异常设计要注意下面的几点:
2.1、自定义异常父类的选择
A、自定义异常的父类,可以选择为RuntimeException或Exception。RuntimeException是运行时异常,你可以选择它来做为你的异常父类,因为这种异常不受到编译器检查,因此,给予了程序员很大的灵活性,程序员可以处理这种异常,也可以不处理(实际上并不是不处理,而是不立即处理,等到一个合适的地方再进行处理)。选择RuntimeException作为父类,是很多框架常采用的,如果你是做底层框架的,可以选择 RuntimeException。
注意:本着简洁高效的设计原则,尽量少实现自己包装的RuntimeException子类,将异常分为几个大类包装即可,多使用最基本的继承了RuntimeException的BaseException
B、业务层异常,一般选择Exception作为父类,因为业务层异常比较重要,一般都是要由调用者进行处理或者是要告知调用者会发生这种异常。如果你的代码是提供给第三方厂商用的,业务层封闭统一的异常就显得非常的有必要。这类异常会强制要求程序员进行处理(异常处理、转译或继续声明抛出),程序完整性、健壮性得到了加强。
例如MVC结构中,针对service层函数:
public Map
public Map
在action层中,调用getIndexInfo(..) 函数的方法不强制要求捕获异常,但是调用getIndexInfo2(..) 函数的方法则强制要求捕获异常。原因是IOException为unchecked Exception,系统不强制要求捕获。
2.2、业务层自定义异常结构的设计
A、业务层自定义异常可以考虑按子系统来划分,也就是说,每一个子系统(模块)都有自己的异常定义,每个子系统自己维护自己的,统一向调用者抛出。
B、根据业务类型,从逻辑上划分异常类型,比如:权限相关的,安全相关的,数据库相关的等等。
总的来说,这两种自定义异常也可以混合使用,因为有的时候,子系统(业务模块)本身就是从逻辑上进行划分的。
注意:
(a)、异常只用来处理异常流程,不得用于处理正常流程。如何区分正常和异常呢?比如验证用户密码不通过、金额超过最大限度、用户名不够长等可预料的错误情况,都属于业务流程中的正常流程,而那些出乎我们预料的、不在业务流程考虑范围内但确实可以对程序的正常运行造成影响的事件,统称为异常情况(例如网络中断、硬盘空间不足),需要使用try catch做异常处理,避免造成更大的损失。这种处理由于不知道错误原因,大多数情况下可以使用简单的记录日志或强制程序终止执行的办法进行处理;
(b)、不允许使用一种异常类处理一种业务错误的方法处理业务逻辑。原因:
(1)该方法会使系统内会产生大量的异常错误类,可能导致重复的异常错误类定义,不便于程序员之间的配合;
(2)一种异常类处理一种业务错误的方法会有大量的try catch语句产生,代码量并不少于if else判断,代码显得尤为凌乱,不便于扩展进行一些统一处理,如日志等。
(c)、在抛出异常使事务回滚的过程中,需要注意配置哪些异常进行回滚,因为spring默认只回滚RuntiomeException类型的。
(d)、使用javascript做错误验证是不可靠的作法,因为用户可以跳过javascript验证。关键性验证必须使用java代码在服务器端做验证,为了验证的快速,提升用户体验,也可以同时在页面用javascript做验证。
2.3、异常结构定义
异常类的父类选定后,再定义自己的异常结构。一般的异常类中,要定义这么一些东西。
A、描述字符串,说明异常的起因,或说明等。
B、异常码。定义一个int或String类型的异常码,异常码在整个系统中统一定义,根据异常继承结构,异常码也可以定义得有层次结构。异常码在大的系统中比较常见,Oracle ,Sqlserver等数据库产品中,或华为、中兴的一些驱动api中。
C、定义一个Throwable的成员变量,用以封装异常结构链。
D、定义无参数、有参数(String,Throwable)的构造方法。
例如:如下类BusinessException.java
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String code;
public BusinessException() {
super();
}
public BusinessException(String message) {
super(message);
}
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public BusinessException(Throwable cause) {
super(cause);
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
public BusinessException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
三、异常处理的几种实现:
3.1、在经典的三层架构模型中,通常都是这样来进行异常处理的:
A、持久层一般抛出的是RuntiomeException类型的异常,一般不处理,直接向上抛出。
B、业务层一般要封装自定义异常,统一向外抛出(这里要注意,如果用spring在业务层管理异常,一定要配置好异常回滚类型,因为spring默认只回滚RuntiomeException类型的)。
B2:业务层也可以不定义任何异常,也不进行try catch,如果业务层出现异常将在表现层进行处理及页面跳转。
C、表现层通过try...catch...捕获处理业务层的异常。
C2、表现层也可以不处理异常,将异常交给Spring容器,不过需要实现自定义异常。
C3、其它什么也不做,直接在web.xml中配置errorPage的,但不建议这么做。
3.2、spring mvc三种实现方式,参考:http://cgs1999.iteye.com/blog/1547197
3.2.1、Spring MVC集成异常处理3种方式都可以达到统一异常处理的目标。从3种方式的优缺点比较,若只需要简单的集成异常处理,推荐使用SimpleMappingExceptionResolver即可;若需要集成的异常处理能够更具个性化,提供给用户更详细的异常信息,推荐自定义实现HandlerExceptionResolver接口的方式;若不喜欢Spring配置文件或要实现“零配置”,且能接受对原有代码的适当入侵,则建议使用@ExceptionHandler注解方式。
3.2.2、注意对于未捕获异常的处理
//web.xml
java.lang.Throwable
/500.jsp
500
/500.jsp
404
/404.jsp
四、Java的异常处理
Java的异常处理是通过5个关键字来实现的:try,catch,throw,throws,finally。JB的在线帮助中对这几个关键字是这样解释的:
Throws: Lists the exceptions a method could throw.
Throw: Transfers control of the method to the exception handler.
Try: Opening exception-handling statement. 打开异常处理语句
Catch: Captures the exception.
Finally: Runs its code before terminating the program.
4.1、try语句
try语句用大括号{}指定了一段代码,该段代码可能会抛弃一个或多个例外。
4.2、catch语句
(1)catch语句的参数类似于方法的声明,包括一个例外类型和一个例外对象。例外类型必须为Throwable类的子类,它指明了catch语句所处理的例外类型,例外对象则由运行时系统在try所指定的代码块中生成并被捕获,大括号中包含对象的处理,其中可以调用对象的方法。
(2)catch语句可以有多个,分别处理不同类的例外。Java运行时系统从上到下分别对每个catch语句处理的例外类型进行检测,直到找到类型相匹配的catch语句为止。这里,类型匹配指catch所处理的例外类型,与生成的例外对象的类型完全一致,或者是生成的例外对象的父类,因此,catch语句的排列顺序应该是从特殊到一般。
例如:
try{
......
}catch(RuntimeException e){
......
}catch(...){
......
}
由于RuntimeException是IOException的父类,当try块中抛出IOException时,会由catch(RuntimeException e)块处理。
(3)也可以用一个catch语句处理多个例外类型,这时它的例外类型参数应该是这多个例外类型的父类,程序设计中要根据具体的情况来选择catch语句的例外处理类型。
(4)catch块中异常的两种处理:
(a)消灭异常;
(b)将异常转型,然后继续向外抛出。一般是将捕获到的异常转换为自定义异常,然后向外抛出给Spring容器。
4.3、 finally语句
try所限定的代码中,当抛弃一个例外时,其后的代码不会被执行。通过finally语句可以指定一块代码。无论try所指定的程序块中抛弃或不抛弃例外,也无论catch语句的例外类型是否与所抛弃的例外的类型一致,finally所指定的代码都要被执行,它提供了统一的出口。通常在finally语句中可以进行资源的清除工作。如关闭打开的文件等。
4.4、throws语句
(1)throws总是出现在一个函数头中,用来标明该成员函数可能抛出的各种异常。对大多数Exception子类来说,Java 编译器会强迫你声明在一个成员函数中抛出的异常的类型。如果异常的类型是Error或 RuntimeException, 或它们的子类,这个规则不起作用, 因为这在程序的正常部分中是不期待出现的。 如果你想明确地抛出一个RuntimeException,你必须用throws语句来声明它的类型;
(2)多个异常用英文逗号分隔;
(3)throws语句只是说明函数抛出异常的一种可能性,并不一定会抛出。
4.5、throw语句
(1)throw语句会将函数的控制权交给异常处理机制;
(2) throw总是出现在函数体中,用来抛出一个异常(throw肯定会抛出异常,与throws不同)。函数在throw语句后,它后面的非finally块中语句将停止执行(函数的控制权交给异常处理机制),然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。
4.6、throw与throws区别与联系:
区别:
(1)throws出现在方法函数头,表示方法可能抛出一个或多个异常,并不一定会发生这些异常;而throw出现在函数体,是语句抛出一个异常,执行throw则一定抛出了某种异常;
(2)throw要么和try-catch-finally语句配套使用,由方法体内的语句处理;要么与throws配套使用,由处理异常的方法捕获。但throws可以单独使 用,然后再由处理异常的方法捕获。
(3)throws E1,E2,E3 只是告诉程序这个方法可能会抛出这些个异常,方法的调用者可能要处理这些异常。而这些异常E1,E2,E3可能是该函数体产生的。而throw是明确之处这个地方要抛出这个异常。
(4)throw语句用在方法体内,表示抛出异常;throws语句用在方法声明后面,表示再抛出异常,用于告知调用者该方法可能会抛出哪些异常。
联系:
当throw与throws配套使用时,都不是在函数体内去处理异常,真正的处理异常由函数的上层调用处理。