论坛登陆 用户名:  密 码:
设为首页  加入收藏
08年北京名校秋季招生
名牌院校免试入学宽进严出,突破考分限制,名校与你零距离,以下院校按报名先后顺序录取,24小时网上报名覆盖全国
  您现在的位置: 中国教育招生在线 >> IT >> JAVA认证 >> IT正文
一个用于J2EE应用程序的异常处理框架
 作者:佚名     2007-3-14 16:56:34        来源:不详  浏览次数:

 

 

 

 

 

 

 

 

使用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的异常,而是希望把它们包装在unchecked异常中,然后抛出给控制器。有时会出现配置问题,这些问题也不能由客户端进行处理,而应该被当作unchecked异常。所有自定义的unchecked异常都应该扩展自java.lang.RuntimeException类。

表示层上的异常处理

  表示层独自负责决定对一个异常采取什么操作。这种决策涉及到识别抛出异常的错误代码。此外,我们还需要知道在处理错误之后应该把错误消息重定向到哪一界面。

  我们需要对基于异常类型获得错误代码这个过程进行抽象。必要时还应该执行日志记录。我们把这种抽象称之为ExceptionHandler。它基于“四人帮”(Gang of Four,GOF) 外观模式(《Design Patterns》一书中说,该模式是用于“为子系统中的一组接口提供一个统一接口。外观定义了一个更高级别的接口,使子系统变得更加易于使用。”),是用于处理所有派生自BaseAppException的异常的整个异常处理系统的外观。下面给出一个在Struts Action方法中进行异常处理的例子:

try{      ...      DivisionDTO storeDTO = divisionBusinessDelegate        .getDivisionByNum(fromDivisionNum);  }catch(BaseAppException ex){      IExceptionHandler eh = ExceptionHandlerFactory        .getInstance().create();      String expContext = "divisionAction.searchDivision";      ExceptionDTO exDto = eh.handleException(          expContext , userId, ex);      ActionErrors errors = new ActionErrors();          errors.add(ActionErrors.GLOBAL_ERROR            ,new ActionError(            exDto.getMessageCode()));      saveErrors(request, errors);      return actionMapping.findForward(          "SearchAdjustmentPage");  } 

  如果更仔细地观察我们刚刚编写的异常处理代码,您可能会意识到,为每个Struts方法编写的代码是十分相似的,这也是一个问题。我们的目标是尽可能地去掉样板代码。我们需要再次对它进行抽象。

  解决方案是使用模板方法(Template Method)设计模式(引自GOF:“它用于实现一个算法的不变部分,并把可变的算法部分留给子类来实现。”)。我们需要一个包含模板方法形式算法的基类。该算法将包含用于BaseAppException的try-catch块和对dispatchMethod方法的调用,方法实现(委托给派生类)如下面的基于Struts的Action中所示:

public abstract class BaseAppDispatchAction    extends DispatchAction{ ... protected static ThreadLocal    expDisplayDetails = new ThreadLocal();  public ActionForward execute(   ActionMapping mapping,   ActionForm form,   HttpServletRequest request,   HttpServletResponse response) throws Exception{     ...     try{         String actionMethod = request            .getParameter(mapping.getParameter());         finalDestination =dispatchMethod(mapping,           form, request, response,actionMethod);     }catch (BaseAppException Ex) {         ExceptionDisplayDTO expDTO =             (ExceptionDisplayDTO)expDisplayDetails                .get();         IExceptionHandler expHandler =              ExceptionHandlerFactory                 .getInstance().create();         ExceptionDTO exDto = expHandler             .handleException(                 expDTO.getContext(), userId, Ex);         ActionErrors errors = new ActionErrors();         errors.add(ActionErrors.GLOBAL_ERROR,              new ActionError(exDto                 .getMessageCode()));         saveErrors(request, errors);         return mapping.findForward(expDTO             .getActionForwardName());     } catch(Throwable ex){            //log the throwable            //throw ex;     } finally {            expDisplayDetails.set(null);     } 

  在Struts中,DispatchAction::dispatchMethod方法用于把请求转发给正确的Action方法,叫做actionMethod。

  我们假定从一个HTTP请求获得searchDivision作为actionMethod:dispatchMethod将在BaseAppDispatchAction的派生Action类中把请求分派给searchDivision方法。在这里,您可以看到,异常处理仅在基类中完成,而派生类则只实现Action方法。这采用了模板方法设计模式,在该模式中,异常处理部分是保持不变的,而dispatchMethod方法的实际实现(可变部分)则交由派生类完成。

  修改后的Struts Action方法如下所示:

...  String exceptionActionForward =      "SearchAdjustmentPage";  String exceptionContext =      "divisionAction.searchDivision";           ExceptionDisplayDTO expDTO =      new ExceptionDisplayDTO(expActionForward,          exceptionContext);  expDisplayDetails.set(expDTO);  ...  DivisionDTO divisionDTO =divisionBusinessDelegate     .getDivisionByNum(fromDivisionNum);  ... 

  现在它看起来相当清晰。因为异常处理是在一个集中的位置上(BaseAppDispatchAction)完成的,手动错误可能造成的影响也降至最低。

  然而,我们需要设置异常上下文和ActionForward方法的名称,如果有异常出现,请求就会被转发到该方法。我们将在ThreadLocal变量expDisplayDetails中设置这些内容。

  但是,为什么要使用java.lang.ThreadLocal变量呢?expDisplayDetails是BaseAppDispatchActiion类中的一个受保护数据成员,这也是它需要是线程安全的原因。java.lang.ThreadLocal对象在这里便可派上用场。

异常处理程序

  在上一部分中,我们讨论了如何对异常处理进行抽象。下面给出一些应该满足的约束:

  • 识别异常类型并获得相应的错误代码,该错误代码可用于显示一条消息给终端用户。
  • 记录异常。底层的日志记录机制被隐藏,可以基于一些环境属性对其进行配置。

  您可能已经注意到了,我们在表示层中捕捉的惟一异常就是BaseAppException。由于所有checked异常都是BaseAppException的子类,这意味着我们要捕捉BaseAppException的所有派生类。基于类名称来识别错误代码再容易不过了。

//exp is an object of BaseAppException String className = exp.getClass().getName(); 

  可以基于异常类的名称在一个XML文件(exceptioninfo.xml)中对错误代码进行配置。下面给出异常配置的一个例子:

     messagecode.laborconfirmation     true     nologging 

  正如您看到的那样,我们把这个异常变为显式,要使用的消息代码是messagecode.employeeconfirmation。然后,为了实现国际化的目的,可以从ResourceBundle提取实际的消息。我们很清楚,不需要对这类异常执行日志记录,因为它只是一条确认消息,而不是一个应用程序错误。

  让我们看一看上下文敏感异常的一个例子:

     messagecode.recordnotfound     false     true     error 

  在这里,这个表达式的contextind为true。在handleException方法中传递的上下文可用于创建惟一的错误代码。例如,如果我们把order.getOrder当作一个上下文进行传递,结果得到的消息代码就是异常的消息代码和所传递的上下文的串联。因此,我们获得了一个像messagecode.recordnotfound.order.getOrder这样的惟一消息代码。

  对于每个异常来说,可以把exceptioninfo.xml 中的数据封装到一个叫做ExceptionInfoDTO的数据传输对象(data transfer object,DTO)。现在,我们还需要一个占位符,用于缓存这些对象,因为我们不想在异常出现时反复解析XML文件和创建对象。这项工作可以委托给一个叫做ExceptionInfoCache的类来完成,这个类将会在从exceptioninfo.xml文件读取ExceptionInfoDTO对象信息之后缓存所有这些对象。

  现在您是否弄清楚了这整个过程?这种方法的核心部分是ExceptionHandler实现,该实现将使用封装在ExceptionInfoDTO中的数据来获取消息代码,创建ExceptionDTO对象,然后基于在给定异常的ExceptionInfoDTO中指定的日志记录类型来记录它。

  下面是ExceptionHandler实现的handleException方法:

public ExceptionDTO handleException(String userId,       BaseAppException exp) {       ExceptionDTO exDTO = new ExceptionDTO();     ExceptionInfoCache ecache =          ExceptionInfoCache.getInstance();     ExceptionInfo exInfo = ecache       .getExceptionInfo(         ExceptionHelper.getClassName(exp));     String loggingType = null;     if (exInfo != null) {         loggingType = exInfo.getLoggingType();         exDTO.setConfirmation(exInfo             .isConfirmation());         exDTO.setMessageCode(exInfo             .getMessageCode());     }      FileLogger logger = new FileLoggerFactory()         .create();     logger.logException(exp, loggingType); 

  根据不同的业务需求,ExceptionHandler接口可以有多种实现。决定使用何种实现的任务可交由Factory来完成,特别是ExceptionHandlerFactory类。

结束语

  如果缺乏全面的异常处理策略,一些特殊的异常处理块便可能导致出现非标准的错误处理和不可维护的代码。通过使用上面的方法,便可简化J2EE应用程序中的异常处理过程。参考资料

本文的示例代码。

设计模式:外观和模板方法模式的源代码。


责任编辑:lss
  相关新闻
法务会计:下一个热门职业?
使用Buffalo集成Spring写的一个登录实例
我对CCIE R&S中路由技术学习的一个总结
Spring创建一个简单的工作流引擎(图)
Java模板引擎Velocity第一个实例
一个判断session是否过期的小技巧
成为一个成功Jsp程序员的过程
Java 安全:一个实现MD5的简洁的java类
用AJAX+J2EE实现一个网上会议室系统
一个关于UML使用现状的调查报告
我对CCIE R&S中路由技术学习的一个总结
法务会计:下一个热门职业?
使用Buffalo集成Spring写的一个登录实例
Java模板引擎Velocity第一个实例
Spring创建一个简单的工作流引擎(图)
我做的一个计算器小程序,望各位高手批评指正
一个可以显示2002年日历的代码程序
一个通过Java连接MYSQL数据库的代码
一个Oracle数据库链接的JavaBean实例
给初学者的一个数据库连接示例(含驱动和示例代码)-推荐
  评论
现在有100人对本文发表评论
查看所有评论
 
推  荐
 
100本成功必读热销书
热门招生
  北京文理研修学院   前进大学
  北京明园大学   北京建设大学
  北京邮电大学世纪学院   北方工商管理学院
  联想软件定向委培班   香港数码动画学院
  青年企业管理研修学院   北京华夏管理学院
热门培训
网络化办公专家培训认证 电子科技大学软件学院
软件测试工程师培训认证 北大青鸟十大授权培训
IT硬件工程师培训认证班 北京环球雅思荷兰预科
JAVA开发工程师培训 潜能时代IT服务管理培训
网络信息化工程师培训 清华大学继续教育学院
论坛精选
 
有些细节是男人也该注意的风度!最容易读错的字
某强人手机里保存的30条短信 中国十大高薪职业
最感人的十大韩剧经典台词 嫁给工程师的N个理由
爆强!只有一句话的鬼故事 转贴教你如何做妖精
 女人一定要記住的話 女人最好别嫁给最爱的男人
城市联盟
 大连 上海 天津 广州 西安 深圳  天津  青岛  大连  福州  沈阳  青海  连云港  南京  吉林  厦门  威海  辽宁  呼和浩特
Copyright © 2006   www.edu999.com   All rights reserved. 中国教育招生在线  版权所有
北京市通信管理局[2004]字第552号函    京ICP证040442号