旉Q?006-06-01 作者:(x)ShriKant Vashishtha 览ơ数Q? 1184 本文关键字:(x)J2EE, (tng)Struts, (tng)RuntimeException, (tng)design patterns, (tng)最?jng)_?/a>, (tng)异常处理, (tng)包装, (tng)设计模式, (tng)外观, (tng)模板Ҏ(gu) |
|
在大多数Java目中,大部分代码都是样板代码。异常处理就属于此类代码。即使业务逻辑只有3?行代码,用于异常处理的代码也要占10? 20行。本文将讨论如何让异常处理保持简单和直观Q开发h员可以专?j)于开发业务逻辑Q而不是把旉费在编写异常处理的h代码上。本文还说明用于在 J2EE环境中创建和处理异常的基知识和指导原则,q提Z(jin)一些可以用异常解决的业务问题。本文将使用Struts框架作ؓ(f)表示实现Q但该方法适用于Q何表C实现?/p>
(zhn)是否曾l想q,Z么要在编写好的代码块周围攄一个try-catch块,即便明知道无法对q些异常q行什么处理,而只满于把它们攑֜ catch块中Q?zhn)可能想知道,Z么不能把q项工作攑֜一个集中的地方完成Q在大多数情况下Q这个地方对于J2EE应用E序来说是一?a target="_blank">前端控制?/a>? 换句话说Q开发h员(sh)?x)因为它们而受到干扎ͼ因ؓ(f)Ҏ(gu)不必很多地过问它们。但是,如果一个方法名U包含一个throws子句Q会(x)出现什么情况呢Q开发h? 或者必L捉这些异常,或者把它们攑֜自己的方法的throws子句中。这是痛苦的根源!q运的是QJava API有一cd做unchecked exception的异常,它们不必捕捉。但是,仍然存在一个问题:(x)Ҏ(gu)什么来军_哪些是checked异常Q哪些是unchecked异常Q下面给Z 些指导原则:(x)
在大多数J2EE应用E序中,关于针对某个异常应该在哪一界面上显C哪条错误消息的决策只能在表C层中做出。这?x)带来另一个问题:(x)Z么我们不能把q种决策攑֜一个公q地方呢?在J2EE应用E序中,前端控制?/a>是一个进行常见处理的集中位置?/p>
此外Q必L一U用于传播异常的通用机制。异怹需要以一U普适的方式得到处理。ؓ(f)此,我们始终需要在控制器端捕捉基本应用E序异常
BaseAppException。这意味着我们需要把BaseAppException异常Q只有这个异常)(j)攑օ可以抛出checked异常的每个方?
的throws子句中。这里的概念是用多态来隐藏异常的实际实现。我们在控制器中捕捉BaseAppExceptionQ但是所抛出的特定异常实例可?
是几个派生异常类中的L一个。借助于这U方法,可以获得许多异常处理斚w的灵zL:(x) 下面l出抛出checked异常的一个例子:(x) q今为止Q我们已l说明,对于所有可能抛出checked异常q被Controller调用的方法,其throws子句中应该只包含
checked异常。然而,q实际上暗示着我们在throws子句中不能包含其他Q何应用程序异常。但是,如果需要基于catch块中某种cd的异常来?
行业务逻辑Q那又该怎么办呢Q要处理q类情况Q方法还可以抛出一个特定异常。记住,q是一U特例,开发h员绝对不能认是理所当然的。同P此处讨论?
应用E序异常应该扩展BaseAppExceptioncR下面给Z个例子:(x) 所有unchecked异常都应该在web应用E序层次上进行处理。可以在web.xml文g中配|web面Q以便当应用E序中出现unchecked异常Ӟ可以把这个web面昄l终端用戗?/p>
当一个异常v源于另一个外部接口(lgQ时Q应该把它包装到一个特定于应用E序的异怸Qƈq行相应处理?/p>
例子Q?/p>
q里QCopyPropertiesException扩展?jin)java.lang.RuntimeExceptionQ我们将?x)记录它。我们捕
捉的是ExceptionQ而不是copyPropertiesҎ(gu)可以抛出的特定checked异常Q因为对于所有这些异常来_(d)我们都会(x)抛出同一?
unchecked CopyPropertiesException异常?/p>
(zhn)可能想知道Q如果我们ؓ(f)每条错误消息创徏一个异常,异常c自w是否会(x)溢出呢?例如Q如果“Order not
found”是OrderNotFoundException的一条错误消息,(zhn)肯定不?x)让CustomerNotFoundException的错误消
息ؓ(f)“Customer not
found”,理由很明显:(x)q两个异总表同L(fng)意义Q惟一的区别在于用它们的上下文不同。所以,如果可以在处理异常时指定上下文,我们无疑可以把这?
异常合ƈZ个RecordNotFoundException。下面给Z个例子:(x) 在这里,employee.addEmployee上下文将被附加给一个上下文敏感的异常的错误代码Q从而生惟一的错误代码。例如,如果
RecordNotFoundException的错误代码是errorcode.recordnotfoundQ那么这个上下文的最l错误代码将变(sh)ؓ(f)
errorcode.recordnotfound.employee.addEmployeeQ它对于q个上下文是惟一的错误代码?/p>
然而,我们要给Z个警告:(x)如果(zhn)准备在同一个客L(fng)Ҏ(gu)中用多个接口,而且q些接口都可以抛出RecordNotFoundException异常Q?
那么惌知道是哪个实体引发了(jin)q个异常变得十分困难。如果业务接口是公共的,而且可以被各U外部客L(fng)使用Q那么徏议只使用特定的异常,而不使用?
RecordNotFoundExceptionq样的一般性异常。特定于上下文的异常对于Z数据库的可恢复异常来说非常有用,因ؓ(f)在这U情况下Q异?
cdl是相同的,不同的只有它们出现的上下文?/p>
正如前面讨论的那P我们需要定义一个异常基c,叫做BaseAppExceptionQ它包含?jin)所有应用程序异常的默认行ؓ(f)。我们将把这个基
cL到所有可能抛出checked异常的方法的throws子句中。应用程序的所有checked异常都应该是q个基类的子cR有多种定义错误处理抽象?
方式。然而,其中的区别更多地是与业务c而不是与技术有兟뀂对错误处理的抽象可分ؓ(f)以下几类。所有这些异常类都是从BaseAppExceptionz
而来?/p>
表示层独自负责决定对一个异帔R取什么操作。这U决{涉?qing)到识别抛出异常的错误代码。此外,我们q需要知道在处理错误之后应该把错误消息重定向到哪一界面?/p>
我们需要对Z异常cd获得错误代码q个q程q行抽象。必要时q应该执行日志记录。我们把q种抽象UC为ExceptionHandler。它Z“四人帮?Gang of FourQGOF) 外观模式Q?a target="_blank">Design Patterns
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
}
...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应用E序层次上处理unchecked异常
把第三方异常包装到特定于应用E序的异怸
try {
BeanUtils.copyProperties(areaDTO, areaForm);
} catch (Exception se) {
throw new CopyPropertiesException(
"Exception occurred while using
copy properties", se);
}q多异常
try{
...
}catch(BaseAppException ex){
IExceptionHandler eh =ExceptionHandlerFactory
.getInstance().create();
ExceptionDTO exDto = eh.handleException(
"employee.addEmployee", userId, ex);
}J2EE应用E序的异常层ơ结?/h3>
checked异常
unchecked异常
表示层上的异常处?/h3>
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");
}
如果更仔l地观察我们刚刚~写的异常处理代码,(zhn)可能会(x)意识刎ͼ为每个StrutsҎ(gu)~写的代码是十分怼的,q也是一个问题。我们的目标是尽可能地去掉样板代码。我们需要再ơ对它进行抽象?/p>
解决Ҏ(gu)是?a target="_blank">模板Ҏ(gu)QTemplate MethodQ设计模式(引自GOFQ“它用于实现一个算法的不变部分Qƈ把可变的法部分留给子类来实现。”)(j)。我们需要一个包含模板方法Ş式算法的? cR该法包含用于BaseAppException的try-catch块和对dispatchMethodҎ(gu)的调用,Ҏ(gu)实现Q委托给zc)(j)? 下面的基于Struts的Action中所C:(x)
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Ҏ(gu)用于把请求{发给正确的ActionҎ(gu)Q叫做actionMethod?/p>
我们假定从一个HTTPh获得searchDivision作ؓ(f)actionMethodQdispatchMethod在 BaseAppDispatchAction的派生ActioncM把请求分zsearchDivisionҎ(gu)。在q里Q?zhn)可以看到Q异常处理仅在基 cM完成Q而派生类则只实现ActionҎ(gu)。这采用?jin)模板方法设计模式,在该模式中,异常处理部分是保持不变的Q而dispatchMethodҎ(gu)? 实际实现Q可变部分)(j)则交由派生类完成?/p>
修改后的Struts ActionҎ(gu)如下所C:(x)
...
String exceptionActionForward =
"SearchAdjustmentPage";
String exceptionContext =
"divisionAction.searchDivision";
ExceptionDisplayDTO expDTO =
new ExceptionDisplayDTO(expActionForward,
exceptionContext);
expDisplayDetails.set(expDTO);
...
DivisionDTO divisionDTO =divisionBusinessDelegate
.getDivisionByNum(fromDivisionNum);
...
现在它看h相当清晰。因为异常处理是在一个集中的位置?BaseAppDispatchAction)完成的,手动错误可能造成的媄(jing)响也降至最低?/p>
然而,我们需要设|异怸下文和ActionForwardҎ(gu)的名Uͼ如果有异常出玎ͼh׃(x)被{发到该方法。我们将在ThreadLocal变量expDisplayDetails中设|这些内宏V?/p>
但是Qؓ(f)什么要使用java.lang.ThreadLocal变量呢?expDisplayDetails? BaseAppDispatchActiioncM的一个受保护数据成员Q这也是它需要是U程安全的原因。java.lang.ThreadLocal? 象在q里便可z上用场?/p>
在上一部分中,我们讨论?jin)如何对异常处理q行抽象。下面给Z些应该满的U束Q?/p>
(zhn)可能已l注意到?jin),我们在表C层中捕捉的惟一异常是BaseAppException。由于所有checked异常都是 BaseAppException的子c,q意味着我们要捕捉BaseAppException的所有派生类。基于类名称来识别错误代码再Ҏ(gu)不过?jin)?/p>
//exp is an object of BaseAppException
String className = exp.getClass().getName();
可以Z异常cȝ名称在一个XML文gQexceptioninfo.xmlQ中寚w误代码进行配|。下面给出异帔R|的一个例子:(x)
<exception name="EmployeeConfirmationException">
<messagecode>messagecode.laborconfirmation</messagecode>
<confirmationind>true</confirmationind>
<loggingtype>nologging</loggingtype>
</exception>
正如(zhn)看到的那样Q我们把q个异常变(sh)ؓ(f)昑ּQ要使用的消息代码是messagecode.employeeconfirmation。然后,Z(jin)实现国际化的目的Q可以从ResourceBundle提取实际的消息。我们很清楚Q不需要对q类异常执行日志记录Q因为它只是一条确认消息,而不是一个应用程序错误?/p>
让我们看一看上下文敏感异常的一个例子:(x)
<exception name="RecordNotFoundException">
<messagecode>messagecode.recordnotfound</messagecode>
<confirmationind>false</confirmationind>
<contextind>true</contextind>
<loggingtype>error</loggingtype>
</exception>
在这里,q个表达式的contextind为true。在handleExceptionҎ(gu)中传递的上下文可用于创徏惟一的错误代码。例如, 如果我们把order.getOrder当作一个上下文q行传递,l果得到的消息代码就是异常的消息代码和所传递的上下文的串联。因此,我们获得?jin)一个像 messagecode.recordnotfound.order.getOrderq样的惟一消息代码?/p>
对于每个异常来说Q可以把 exceptioninfo.xml 中的数据装C个叫做ExceptionInfoDTO的数据传输对象(data transfer objectQDTOQ。现在,我们q需要一个占位符Q用于缓存这些对象,因ؓ(f)我们不想在异常出现时反复解析XML文g和创建对象。这工作可以委托给一 个叫做ExceptionInfoCache的类来完成,q个cd?x)在从exceptioninfo.xml文gdExceptionInfoDTO? 象信息之后缓存所有这些对象?/p>
现在(zhn)是否弄清楚?jin)这整个q程Q这U方法的核心(j)部分是ExceptionHandler实现Q该实现 用封装在ExceptionInfoDTO中的数据来获取消息代码,创徏ExceptionDTO对象Q然后基于在l定异常? ExceptionInfoDTO中指定的日志记录cd来记录它?/p>
下面是ExceptionHandler实现的handleExceptionҎ(gu)Q?/p>
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);
Ҏ(gu)不同的业务需求,ExceptionHandler接口可以有多U实现。决定用何U实现的d可交由Factory来完成,特别是ExceptionHandlerFactorycR?/p>
如果~Z全面的异常处理策略,一些特D的异常处理块便可能D出现非标准的错误处理和不可维护的代码。通过使用上面的方法,便可化J2EE应用E序中的异常处理q程?/p>
原文出处Q?/strong> http://www.onjava.com/pub/a/onjava/2006/01/11/exception-handling-framework-for-j2ee.html
(tng)作者简?/span> | |
|
ShriKant Vashishtha 当前是印度Tata Consultancy Services Limited (TCS)公司的解x(chng)案架构师?/td> |