`
evanzzy
  • 浏览: 2846 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

基于Spring项目的异常设计

阅读更多
基于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倍。
分享到:
评论
18 楼 evanzzy 2011-01-07  
Spring框架本身的Annotation注入功能在对象重名时有bug
17 楼 smallhand 2011-01-07  
白糖_ 写道
很早前,我一同事就擅长在try中throw Exception,被技术指导骂惨了,当时一直不知道原因,现在明了了。
另外,请问我一个方法体这样写:
public void method()throws Exception{
   try{
      xxx
      xxx
      ...一堆代码
   }catch(Exception e){
     throw e;
   }
}
这种写法是不是非常不规范!?

代码和理解上是没有错误的。不过你既然catch了异常,就是为了对异常进行处理,但是你又直接抛出不做处理,是不是有些多此一举呢?
16 楼 smallhand 2011-01-07  
evanzzy 写道
Zahir 写道
照第3条来说 throw BaseException("账户余额不足!") 这种应该属于正常流程吧, 这样岂不是自我矛盾了


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

根据我的理解来说,如果只是简单的查询:例如用户登录密码验证,这些是不需要异常捕获的,如果有数据的改变则需要使用Spring的事务处理。不必要的异常捕获也增加了代码的可读性。
15 楼 gtssgtss 2011-01-07  
evanzzy 写道
kyfxbl 写道
我严重不同意你这个说法

用annotation来做对象注入极大减轻了组件管理压力,我们这个项目里面有数百个组件,依赖关系也复杂,你用配置文件来注入,那维护的工作量是非常大的。

至于事务嘛。。不好说,如果你用配置文件,那需要配置事务的方法需要有命名规范,这个有时候也是会造成误导的。

总的来说,是否用annotation做事务声明,我保留意见。用annotation做依赖注入,我是严重支持的。spring的趋势本身也是支持annotation


用Annotation做注入看似省事儿,其实有潜在风险和工时开销。主要问题是修改Annotation需要修改代码,而修改了代码就要重新走流程做测试,这个工时开销是比较大的;另外Annotation注入本身也有重名出错的可能,综合算下来,并不比使用配置文件更省事儿。依我看,倒是项目越大,越应该使用配置文件进行注入,项目小了用Annotation注入更有利些。


改annotation就会出错,改配置就不会出错?没这个道理
改配置不测试,出错了怎么办?你承担责任?
都会出错的话,就都测试,没差别
14 楼 evanzzy 2011-01-06  
kyfxbl 写道
我严重不同意你这个说法

用annotation来做对象注入极大减轻了组件管理压力,我们这个项目里面有数百个组件,依赖关系也复杂,你用配置文件来注入,那维护的工作量是非常大的。

至于事务嘛。。不好说,如果你用配置文件,那需要配置事务的方法需要有命名规范,这个有时候也是会造成误导的。

总的来说,是否用annotation做事务声明,我保留意见。用annotation做依赖注入,我是严重支持的。spring的趋势本身也是支持annotation


用Annotation做注入看似省事儿,其实有潜在风险和工时开销。主要问题是修改Annotation需要修改代码,而修改了代码就要重新走流程做测试,这个工时开销是比较大的;另外Annotation注入本身也有重名出错的可能,综合算下来,并不比使用配置文件更省事儿。依我看,倒是项目越大,越应该使用配置文件进行注入,项目小了用Annotation注入更有利些。
13 楼 xieshaohu 2011-01-06  
个人觉得事务控制就是和具体业务相关的,所以采用annotation配置管理事务也没有什么不妥。
12 楼 kyfxbl 2011-01-06  
evanzzy 写道
kevin1988620 写道
其实spring事务会滚,只是默认支持runntime,可以设置让他支持exception的,如可以在方法上添加如下注解
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)


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

另外非常不推荐使用Annotation配置事务声明和对象注入,这样无法使用配置文件进行统一的事务控制,容易产生混乱。


我严重不同意你这个说法

用annotation来做对象注入极大减轻了组件管理压力,我们这个项目里面有数百个组件,依赖关系也复杂,你用配置文件来注入,那维护的工作量是非常大的。

至于事务嘛。。不好说,如果你用配置文件,那需要配置事务的方法需要有命名规范,这个有时候也是会造成误导的。

总的来说,是否用annotation做事务声明,我保留意见。用annotation做依赖注入,我是严重支持的。spring的趋势本身也是支持annotation
11 楼 evanzzy 2011-01-05  
白糖_ 写道
很早前,我一同事就擅长在try中throw Exception,被技术指导骂惨了,当时一直不知道原因,现在明了了。
另外,请问我一个方法体这样写:
public void method()throws Exception{
   try{
      xxx
      xxx
      ...一堆代码
   }catch(Exception e){
     throw e;
   }
}
这种写法是不是非常不规范!?


呵呵,一般来说这是一种偷懒的写法,当然不好。在方法中抓到了exception,然后没有做任何处理原封不动的抛了出去,这有什么意义呢?直接声明throw Exception让上层代码去处理就可以了。而且一般来说,抓到了exception要尽量处理掉,实在处理不掉才可以往上层抛。就事论事的讲这段代码,直接捕捉根异常也是不合适的,应该把异常从低到高的逐层捕捉,逐层处理。另外,catch里面捕捉到异常一般来说是要用Log4j记录,甚至记录到数据库里面,否则运维人员无法查错啊。
10 楼 evanzzy 2011-01-05  
spiritfrog 写道
evanzzy 写道
Zahir 写道
照第3条来说 throw BaseException("账户余额不足!") 这种应该属于正常流程吧, 这样岂不是自我矛盾了


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


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



对,就是这个意思。“用方法返回值区分不同的情况”需要解释一下,方法的返回值不仅限于0、1、xx这类代码,这样返回错误代码程序结构是不太清晰的,可以通过方法的拆分和重构避免这类情况出现,返回值也可以是vo、map、list。另外有一种情况要注意,一般出现在两个系统的互相调用时,例如银行前置机和企业的ERP做接口,银行方只能返回结果代码,返回其他的东西或抛异常是不合适的。
9 楼 evanzzy 2011-01-05  
kevin1988620 写道
其实spring事务会滚,只是默认支持runntime,可以设置让他支持exception的,如可以在方法上添加如下注解
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)


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

另外非常不推荐使用Annotation配置事务声明和对象注入,这样无法使用配置文件进行统一的事务控制,容易产生混乱。
8 楼 白糖_ 2011-01-05  
很早前,我一同事就擅长在try中throw Exception,被技术指导骂惨了,当时一直不知道原因,现在明了了。
另外,请问我一个方法体这样写:
public void method()throws Exception{
   try{
      xxx
      xxx
      ...一堆代码
   }catch(Exception e){
     throw e;
   }
}
这种写法是不是非常不规范!?
7 楼 spiritfrog 2011-01-05  
evanzzy 写道
Zahir 写道
照第3条来说 throw BaseException("账户余额不足!") 这种应该属于正常流程吧, 这样岂不是自我矛盾了


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


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

6 楼 kangsoft 2011-01-05  
为什么往外抛的异常的方法都没有thows??
5 楼 kevin1988620 2011-01-05  
其实spring事务会滚,只是默认支持runntime,可以设置让他支持exception的,如可以在方法上添加如下注解
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
4 楼 evanzzy 2011-01-05  
Zahir 写道
照第3条来说 throw BaseException("账户余额不足!") 这种应该属于正常流程吧, 这样岂不是自我矛盾了


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

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


evanzzy 写道

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

照第3条来说 throw BaseException("账户余额不足!") 这种应该属于正常流程吧, 这样岂不是自我矛盾了
2 楼 evanzzy 2011-01-04  
finallygo 写道
只是try{}catch不会对性能有影响吧

try catch影响是小的,只是建立exception类和捕获exception对象会有较大的性能开销,应该避免。因为一般我们需要做异常处理的时候才使用try catch包裹代码,所以在这里我使用了try catch指代代码中的使用异常处理业务逻辑的编码方式
1 楼 finallygo 2011-01-04  
只是try{}catch不会对性能有影响吧

相关推荐

Global site tag (gtag.js) - Google Analytics