首页 > 程序开发 > 软件开发 > Java >

一个用于J2EE应用程序的异常处理框架

2006-09-21

使用checked和unchecked异常的场景   您是否曾经想过,为什么要在编写好的代码块周围放置一个try-catch块,即便明知道无法对这些异常进行什么处理,而只满足于把它们放在catch块中?您可能想知道,为什么不能把这

使用checked和unchecked异常的场景

  您是否曾经想过,为什么要在编写好的代码块周围放置一个try-catch块,即便明知道无法对这些异常进行什么处理,而只满足于把它们放在catch块中?您可能想知道,为什么不能把这项工作放在一个集中的地方完成?在大多数情况下,这个地方对于J2EE应用程序来说就是一个前端控制器。换句话说,开发人员不会因为它们而受到干扰,因为根本不必很多地过问它们。但是,如果一个方法名称包含一个throws子句,会出现什么情况呢?开发人员或者必须捕捉这些异常,或者把它们放在自己的方法的throws子句中。这就是痛苦的根源!幸运的是,Java API有一类叫做unchecked exception的异常,它们不必捕捉。但是,仍然存在一个问题:根据什么来决定哪些是checked异常,哪些是unchecked异常?下面给出一些指导原则:

  • 终端用户无法采取有效操作的异常应该作为unchecked异常。例如,致命的和不可恢复的异常就应该是unchecked。把XMLParseException(在解析XML文件时抛出)作为checked异常没有任何意义,因为惟一能够采取的措施就是基于异常跟踪来解决根本问题。通过扩展java.lang.RuntimeException,可以创建自定义的unchecked异常。
  • 在应用程序中,与用户操作相关的异常应该是checked异常。checked异常要求客户端来捕捉它们。您可能会问,为什么不把所有异常都当作是unchecked。这样做的问题在于,其中一些异常无法在正确的位置被捕捉到。这会带来更大的问题,因为错误只有在运行时才能被识别。checked异常的例子有业务确认异常、安全性异常等等。

异常抛出策略

只捕捉基本应用程序异常(假定为BaseAppException)并在throws子句中声明

  在大多数J2EE应用程序中,关于针对某个异常应该在哪一界面上显示哪条错误消息的决策只能在表示层中做出。这会带来另一个问题:为什么我们不能把这种决策放在一个公共的地方呢?在J2EE应用程序中,前端控制器就是一个进行常见处理的集中位置。

  此外,必须有一种用于传播异常的通用机制。异常也需要以一种普适的方式得到处理。为此,我们始终需要在控制器端捕捉基本应用程序异常BaseAppException。这意味着我们需要把BaseAppException异常(只有这个异常)放入可以抛出checked异常的每个方法的throws子句中。这里的概念是使用多态来隐藏异常的实际实现。我们在控制器中捕捉BaseAppException,但是所抛出的特定异常实例可能是几个派生异常类中的任意一个。借助于这种方法,可以获得许多异常处理方面的灵活性:

  • 不需要在throws子句中放入大量的checked异常。throws子句中只需要有一个异常。
  • 不需要再对应用程序异常使用混乱的catch块。如果需要处理它们,一个catch块(用于BaseAppException)就足够了。
  • 开发人员不需要亲自进行异常处理(日志记录以及获取错误代码)。这种抽象是由ExceptionHandler完成的,稍后本文会就此点进行讨论。
  • 即使稍后把更多异常引入到方法实现中,方法名称也不会改变,因此也不需要修改客户端代码,否则就会引起连锁反应。然而,抛出的异常需要在方法的Javadoc中指定,以便让客户端可以看到方法约束。

  下面给出抛出checked异常的一个例子:

public void updateUser(UserDTO userDTO)    throws BaseAppException{     UserDAO userDAO = new UserDAO();     UserDAO.updateUser(userDTO);     ...     if(...)         throw new RegionNotActiveException(             "Selected region is not active"); }  Controller Method: ... try{     User user = new User();     user.updateUser(userDTO); }catch(BaseAppException ex){     //ExceptionHandler is used to handle     //all exceptions derived from BaseAppException } ... 

  迄今为止,我们已经说明,对于所有可能抛出checked异常并被Controller调用的方法,其throws子句中应该只包含checked异常。然而,这实际上暗示着我们在throws子句中不能包含其他任何应用程序异常。但是,如果需要基于catch块中某种类型的异常来执行业务逻辑,那又该怎么办呢?要处理这类情况,方法还可以抛出一个特定异常。记住,这是一种特例,开发人员绝对不能认为这是理所当然的。同样,此处讨论的应用程序异常应该扩展BaseAppException类。下面给出一个例子:

CustomerDAO method: //throws CustomerNotActiveException along with //BaseAppException public CustomerDTO getCustomer(InputDTO inputDTO)     throws BaseAppException,         CustomerNotActiveException {     . . .     //Make a DB call to fetch the customer      //details based o­n inputDTO     . . .     // if not details found     throw new CustomerNotActiveException(         "Customer is not active"); }  Client method:  //catch CustomerNotActiveException //and continues its processing public CustomerDTO getCustomerDetails(   UserDTO userDTO)     throws BaseAppException{     ...     CustomerDTO custDTO = null;     try{         //Get customer details          //from local database         customerDAO.getCustomerFromLocalDB();     }catch(CustomerNotActiveException){         ...         return customerDAO             .activateCustomerDetails();     } } 

在web应用程序层次上处理unchecked异常

  所有unchecked异常都应该在web应用程序层次上进行处理。可以在web.xml文件中配置web页面,以便当应用程序中出现unchecked异常时,可以把这个web页面显示给终端用户。

把第三方异常包装到特定于应用程序的异常中

  当一个异常起源于另一个外部接口(组件)时,应该把它包装到一个特定于应用程序的异常中,并进行相应处理。

  例子:

try {     BeanUtils.copyProperties(areaDTO, areaForm); } catch (Exception se) {     throw new CopyPropertiesException(         "Exception occurred while using              copy properties", se); } 

  这里,CopyPropertiesException扩展了java.lang.RuntimeException,我们将会记录它。我们捕捉的是Exception,而不是copyProperties方法可以抛出的特定checked异常,因为对于所有这些异常来说,我们都会抛出同一个unchecked CopyPropertiesException异常。

过多异常

  您可能想知道,如果我们为每条错误消息创建一个异常,异常类自身是否会溢出呢?例如,如果“Order not found”是OrderNotFoundException的一条错误消息,您肯定不会让CustomerNotFoundException的错误消息为“Customer not found”,理由很明显:这两个异常代表同样的意义,惟一的区别在于使用它们的上下文不同。所以,如果可以在处理异常时指定上下文,我们无疑可以把这些异常合并为一个RecordNotFoundException。下面给出一个例子:

try{     ... }catch(BaseAppException ex){     IExceptionHandler eh =ExceptionHandlerFactory       .getInstance().create();     ExceptionDTO exDto = eh.handleException(         "employee.addEmployee", userId, ex); } 

  在这里,employee.addEmployee上下文将被附加给一个上下文敏感的异常的错误代码,从而产生惟一的错误代码。例如,如果RecordNotFoundException的错误代码是errorcode.recordnotfound,那么这个上下文的最终错误代码将变为errorcode.recordnotfound.employee.addEmployee,它对于这个上下文是惟一的错误代码。

  然而,我们要给出一个警告:如果您准备在同一个客户端方法中使用多个接口,而且这些接口都可以抛出RecordNotFoundException异常,那么想要知道是哪个实体引发了这个异常就变得十分困难。如果业务接口是公共的,而且可以被各种外部客户端使用,那么建议只使用特定的异常,而不使用像RecordNotFoundException这样的一般性异常。特定于上下文的异常对于基于数据库的可恢复异常来说非常有用,因为在这种情况下,异常类始终是相同的,不同的只有它们出现的上下文。

J2EE应用程序的异常层次结构

  正如前面讨论的那样,我们需要定义一个异常基类,叫做BaseAppException,它包含了所有应用程序异常的默认行为。我们将把这个基类放到所有可能抛出checked异常的方法的throws子句中。应用程序的所有checked异常都应该是这个基类的子类。有多种定义错误处理抽象的方式。然而,其中的区别更多地是与业务类而不是与技术有关。对错误处理的抽象可分为以下几类。所有这些异常类都是从BaseAppException派生而来。

checked异常

  • 业务异常:执行业务逻辑时出现的异常。BaseBusinessException是这类异常的基类。
  • 数据库异常:与持久化机制进行交互时抛出的异常。BaseDBException是这类异常的基类。
  • 安全性异常:执行安全性操作时出现的异常。这类异常的基类是BaseSecurityException。
  • 确认异常:在从终端用户处获得确认以执行某个特定任务时使用。这类异常的基类是BaseConfirmationException。

unchecked异常

  • 系统异常:有时候我们希望使用unchecked异常。例如下面的情况:不想亲自处理来自第三方库API的异常,而是希望
相关文章
最新文章
热点推荐