论坛首页 Java企业应用论坛

基于Spring项目的异常设计

浏览 9655 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-01-04   最后修改:2011-01-05
基于Spring项目的异常设计


1、只使用包装过的RuntimeException,不使用CheckedException。原因:Spring框架本身放弃了CheckedException这个比较有争议的异常类,而且Spring的事务管理需要捕捉RuntimeException的子类才能实现事务回滚;

2、本着简洁高效的设计原则,尽量少实现自己包装的RuntimeException子类,将异常分为几个大类包装即可,多使用最基本的继承了RuntimeException的BaseException;

3、异常只用来处理异常流程,不得用于处理正常流程。如何区分正常和异常呢?比如验证用户密码不通过、金额超过最大限度、用户名不够长等可预料的错误情况,都属于业务流程中的正常流程,而那些出乎我们预料的、不在业务流程考虑范围内但确实可以对程序的正常运行造成影响的事件,统称为异常情况(例如网络中断、硬盘空间不足),需要使用try catch做异常处理,避免造成更大的损失。这种处理由于不知道错误原因,大多数情况下可以使用简单的记录日志或强制程序终止执行的办法进行处理;

4、不允许使用一种异常类处理一种业务错误的方法处理业务逻辑。原因:系统内会产生大量的异常错误类,不便于程序员之间的配合,可能导致重复的异常错误类定义;大量的try catch语句产生的代码量并不少于if else判断,而且造成了严重的性能损失,必须坚决摒弃;

5、在抛出异常使事务回滚的过程中,异常本身同时带有错误提示信息,例如throw new BaseException("账户余额不足!"),在Action层将异常截获,并将异常内错误提示信息放入request内返回页面,由统一的页面处理程序将信息取出,并使用javascript等方式弹出显示给用户;

6、使用javascript做错误验证是不可靠的作法,用户可以使用firebug跳过javascript验证。关键性验证必须使用java代码在服务器端做验证,为了验证的快速,提升用户体验,也可以同时在页面用javascript做验证。


关于使用异常的性能问题,有很多讨论,我们可以通过测试代码检测一下使用抛异常的方式处理业务逻辑到底会有多大的性能损失。以下代码摘抄自《透过JVM看 Exception本质》http://www.iteye.com/topic/857722,这篇文章分析的很专业,建议看一下。

package org.fenixsoft.exception;   
  
public class ExceptionTest {   
  
    private int testTimes;   
  
    public ExceptionTest(int testTimes) {   
        this.testTimes = testTimes;   
    }   
  
    public void newObject() {   
        long l = System.nanoTime();   
        for (int i = 0; i < testTimes; i++) {   
            new Object();   
        }   
        System.out.println("建立对象:" + (System.nanoTime() - l));   
    }   
  
    public void newException() {   
        long l = System.nanoTime();   
        for (int i = 0; i < testTimes; i++) {   
            new Exception();   
        }   
        System.out.println("建立异常对象:" + (System.nanoTime() - l));   
    }   
  
    public void catchException() {   
        long l = System.nanoTime();   
        for (int i = 0; i < testTimes; i++) {   
            try {   
                throw new Exception();   
            } catch (Exception e) {   
            }   
        }   
        System.out.println("建立、抛出并接住异常对象:" + (System.nanoTime() - l));   
    }   
  
    public static void main(String[] args) {   
        ExceptionTest test = new ExceptionTest(10000);   
        test.newObject();   
        test.newException();   
        test.catchException();   
    }   
}  


运行结果:
引用

建立对象:575817 
建立异常对象:9589080 
建立、抛出并接住异常对象:4739447

所以我们说使用抛异常的方式处理业务逻辑会造成大量性能损失,这个性能损失的数量级一般要超过正常处理的10倍。
   发表时间:2011-01-04  
只是try{}catch不会对性能有影响吧
0 请登录后投票
   发表时间:2011-01-04  
finallygo 写道
只是try{}catch不会对性能有影响吧

try catch影响是小的,只是建立exception类和捕获exception对象会有较大的性能开销,应该避免。因为一般我们需要做异常处理的时候才使用try catch包裹代码,所以在这里我使用了try catch指代代码中的使用异常处理业务逻辑的编码方式
0 请登录后投票
   发表时间:2011-01-05  
evanzzy 写道

3、异常只用来处理异常流程,不得用于处理正常流程。如何区分正常和异常呢?比如验证用户密码不通过、金额超过最大限度、用户名不够长等可预料的错误情况,都属于业务流程中的正常流程,而那些出乎我们预料的、不在业务流程考虑范围内但确实可以对程序的正常运行造成影响的事件,统称为异常情况(例如网络中断、硬盘空间不足),需要使用try catch做异常处理,避免造成更大的损失。这种处理由于不知道错误原因,大多数情况下可以使用简单的记录日志或强制程序终止执行的办法进行处理;


evanzzy 写道

5、在抛出异常使事务回滚的过程中,异常本身同时带有错误提示信息,例如throw BaseException("账户余额不足!"),在Action层将异常截获,并将异常内错误提示信息放入request内返回页面,由统一的页面处理程序将信息取出,并使用javascript等方式弹出显示给用户;

照第3条来说 throw BaseException("账户余额不足!") 这种应该属于正常流程吧, 这样岂不是自我矛盾了
0 请登录后投票
   发表时间:2011-01-05  
Zahir 写道
照第3条来说 throw BaseException("账户余额不足!") 这种应该属于正常流程吧, 这样岂不是自我矛盾了


这是不矛盾的,我解释一下。因为Spring的事务回滚机制要求必须抛出异常才能使事务回滚,所以出错了必须抛异常。而异常中带有错误信息,是为了简化设计和代码实现。我们也可以抛出一个异常使Spring事务回滚,然后再返回一条错误信息,这样也是正确的,但是要多写代码,增加开发成本,综合考虑起来,所以在异常中带有错误信息然后让页面去捕获,这样做既使Spring的事务回滚,又同时实现了给用户的错误提示
0 请登录后投票
   发表时间:2011-01-05  
其实spring事务会滚,只是默认支持runntime,可以设置让他支持exception的,如可以在方法上添加如下注解
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
0 请登录后投票
   发表时间:2011-01-05  
为什么往外抛的异常的方法都没有thows??
0 请登录后投票
   发表时间:2011-01-05  
evanzzy 写道
Zahir 写道
照第3条来说 throw BaseException("账户余额不足!") 这种应该属于正常流程吧, 这样岂不是自我矛盾了


这是不矛盾的,我解释一下。因为Spring的事务回滚机制要求必须抛出异常才能使事务回滚,所以出错了必须抛异常。而异常中带有错误信息,是为了简化设计和代码实现。我们也可以抛出一个异常使Spring事务回滚,然后再返回一条错误信息,这样也是正确的,但是要多写代码,增加开发成本,综合考虑起来,所以在异常中带有错误信息然后让页面去捕获,这样做既使Spring的事务回滚,又同时实现了给用户的错误提示


是不是这样理解更好点:
1)如果出现意外,程序需要终止,或请求需要终止(如账户余额不足,不能继续操作了,页面将返回该提示信息), 就是用封装好的unchecked异常
2)如果出现了意外,但程序、请求并不要终止, 那么就不使用异常,而是用方法返回值区分不同的情况,对该返回值进行不同处理流程
3)异常不再用checked,而是用unchecked,这样一来都不会需要有显式的try...catch,而统一交给action处理了。

0 请登录后投票
   发表时间:2011-01-05  
很早前,我一同事就擅长在try中throw Exception,被技术指导骂惨了,当时一直不知道原因,现在明了了。
另外,请问我一个方法体这样写:
public void method()throws Exception{
   try{
      xxx
      xxx
      ...一堆代码
   }catch(Exception e){
     throw e;
   }
}
这种写法是不是非常不规范!?
0 请登录后投票
   发表时间:2011-01-05  
kevin1988620 写道
其实spring事务会滚,只是默认支持runntime,可以设置让他支持exception的,如可以在方法上添加如下注解
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)


Spring设计者在架构中不推荐CheckedException,而且这个异常类也比较有争议,增加了程序的复杂性,我们使用Spring做项目,还是按照Spring的理念来做比较好,尽量做到少改、不改。

另外非常不推荐使用Annotation配置事务声明和对象注入,这样无法使用配置文件进行统一的事务控制,容易产生混乱。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics