`
srj2903
  • 浏览: 107129 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

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

    博客分类:
  • java
阅读更多

一个用于J2EE应用程序的异常处理框架
 
2009-02-26 作者:ShriKant Vashishtha 来源:bea
 
 
 在大多数Java项目中,大部分代码都是样板代码。异常处理就属于此类代码。即使业务逻辑只有3到4行代码,用于异常处理的代码也要占10到20行。本文将讨论如何让异常处理保持简单和直观,使开发人员可以专心于开发业务逻辑,而不是把时间浪费在编写异常处理的样板代码上。本文还将说明用于在J2EE环境中创建和处理异常的基础知识和指导原则,并提出了一些可以使用异常解决的业务问题。本文将使用Struts框架作为表示实现,但该方法适用于任何表示实现。

使用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 on 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)中对错误代码进行配置。下面给出异常配置的一个例子:

<exception name="EmployeeConfirmationException">
 <messagecode>messagecode.laborconfirmation</messagecode>
 <confirmationind>true</confirmationind>
 <loggingtype>nologging</loggingtype>
</exception>

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

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

<exception name="RecordNotFoundException">
 <messagecode>messagecode.recordnotfound</messagecode>
 <confirmationind>false</confirmationind>
 <contextind>true</contextind>
 <loggingtype>error</loggingtype>
</exception>

在这里,这个表达式的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应用程序中的异常处理过程。
 

分享到:
评论

相关推荐

    j2ee框架笔记详细介绍j2ee的框架结构

    J2EE(Java 2 Platform, Enterprise Edition)是Java平台的企业版,它为开发和部署分布式、多层的企业级应用程序提供了全面的框架。J2EE框架的核心在于其组件模型,包括Servlet、JSP(JavaServer Pages)、EJB...

    J2EE项目实训——Struts框架技术.rar

    7. **异常处理**:Struts框架提供了全局的异常处理机制,可以定义一个或多个错误页面,用于捕获并处理应用程序中抛出的异常。 8. **拦截器(Interceptor)**:拦截器是Struts2引入的重要特性,它们是实现了特定接口...

    J2EE框架学习笔记

    在IT行业中,J2EE(Java 2 Platform, Enterprise Edition)是一个广泛使用的后端开发平台,主要用于构建企业级分布式应用程序。本篇学习笔记将聚焦于四个核心的J2EE框架:JDBC、Hibernate、Struts和Spring,这些框架...

    J2EE 企业及测试案例(设计框架应用开发)

    《J2EE企业及测试案例(设计框架应用开发)》是一个综合性的学习资源,主要针对J2EE平台上的企业级应用程序开发和测试。这个资源详细介绍了J2EE的基础知识,适合初学者入门学习。以下是一些核心知识点的概述: 1. *...

    J2EE 指南 J2EE中文教材

    - **创建J2EE应用程序**:指导如何创建一个基本的J2EE应用程序。 - **创建企业Bean**:具体阐述了如何定义和实现EJB。 - **创建J2EE应用程序客户端**:说明了如何为EJB应用程序创建客户端。 - **创建Web客户端**...

    java笔记 Java-Web笔记 J2EE三大框架笔记

    Struts是MVC(Model-View-Controller)架构的实现,负责控制应用程序流程,处理用户请求。Hibernate则是ORM(Object-Relational Mapping)框架,实现了Java对象与数据库表之间的映射,极大地减少了数据库操作的复杂...

    J2EE 三大框架开发要用到的 不冲突的JAR包

    在J2EE应用程序开发中,三大主流框架SSH(Struts、Spring、Hibernate)是不可或缺的组件,它们分别负责表现层、业务逻辑层和数据持久化层。这些框架的使用极大地提高了开发效率,降低了代码的耦合度。在这个“J2EE...

    j2ee学习经验和流程

    这是因为J2SE为开发者提供了Java语言的基本功能,如类和对象的概念、异常处理、线程等,这些都是构建复杂企业应用所必需的知识点。 #### 三、学习工具的选择 在学习过程中,选择合适的集成开发环境(IDE)是非常...

    使用J2EE开发企业应用

    它建立在Java SE的基础上,提供了一套完整的、面向服务的企业应用程序框架。J2EE的主要目标是简化企业级应用的开发、部署和管理。 #### J2EE的组件与技术 1. **Servlets**:用于处理客户端请求并生成动态网页内容。...

    J2EE学习经验和流程

    J2EE是Java技术平台的一个分支,专注于企业级应用开发,涵盖了Web应用程序、企业信息系统、中间件服务等。它与J2SE(Java 2 Platform, Standard Edition)和J2ME(Java 2 Platform, Micro Edition)并列,分别针对...

    j2ee程序设计(基础)

    在IT行业中,J2EE(Java 2 Platform, Enterprise Edition)是一种广泛使用的开源框架,用于构建企业级的分布式应用程序。这个“j2ee程序设计(基础)”的学习资源旨在为初学者提供一个全面且基础的入门平台,帮助...

    j2ee 登录小程序

    J2EE(Java 2 Platform, Enterprise Edition)是一个由Oracle公司维护的开源框架,用于构建企业级的分布式应用程序。它提供了丰富的服务、API和组件模型,以支持Web应用程序的开发,包括登录系统。在这个特定的...

    企业中的Java安全策略 建立安全可靠的J2EE应用程序.zip

    在企业环境中,构建安全可靠的J2EE应用程序是至关重要的,因为这些系统往往处理着大量敏感数据和关键业务流程。Java安全策略是确保这种环境安全性的基石。本资料集围绕"企业中的Java安全策略"展开,旨在帮助开发者和...

    J2EE应用编程精典150例

    在Java世界中,J2EE(Java 2 Platform, Enterprise Edition)是企业级开发的重要框架,它为构建分布式、多层的Web应用程序提供了强大的支持。本资料“J2EE应用编程精典150例”显然是针对那些希望深入理解和掌握J2EE...

    简单的j2ee项目

    通过Eclipse配置Tomcat,开发者可以在本地环境中测试和调试J2EE应用程序,确保其正常运行。 以下是可能涉及的知识点: 1. **Java基础知识**:首先,理解J2EE项目需要扎实的Java语言基础,包括面向对象编程、异常...

    j2EE三大框架全面笔记

    Java 2 Platform, Enterprise Edition (J2EE) 是一个用于构建企业级分布式应用程序的平台,其三大核心框架包括:Servlet、JavaServer Pages (JSP) 和 JavaBeans (JDBC)。这三大框架构成了Web应用程序的基础架构,...

    基于Struts和Hibernate的J2EE应用

    通过深入理解和实践这些知识点,开发者可以构建出高效、可扩展的J2EE应用程序,同时享受到Struts和Hibernate带来的便利和灵活性。在实际项目中,还需要考虑性能优化、安全策略以及与其他J2EE组件(如Spring、EJB等)...

    J2EE应用开发基础

    - **基础知识**:介绍如何将J2EE应用程序打包成可部署的形式。 - **类装载模式**:讨论了不同类型的类装载器以及它们如何加载类。 - **配置J2EE包** - **企业应用的开发过程**:从创建到部署的整个流程。 - **...

    java 开发 J2EE企业级应用

    Java J2EE(Java 2 Platform, Enterprise Edition)是Java平台的一个版本,专门设计用于构建企业级应用程序。这个平台提供了一套丰富的API和服务,支持分布式、多层架构的应用开发,包括Web服务、数据库连接、事务...

    一个j2ee的开发案例程序

    4. **Application Server**:例如Tomcat、GlassFish或JBoss,这些服务器提供运行J2EE应用程序所需的环境,包括容器管理的服务,如事务处理、安全性和资源连接池。 5. **数据库**:如MySQL、Oracle或PostgreSQL,...

Global site tag (gtag.js) - Google Analytics