一个用于J2EE应用程序的异常处理框架
【IT168 技术文档】
在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);
}
