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