在一个framework的设计中,异常框架的设计占据着很重要的位置,因为它会直接影响到整个应用的健壮性、稳定性和易用性,因此笔者结合自己在产品开发中的经验给出了一个异常框架的设计及实现与大家共享,有考虑不周或欠妥的地方还望大家一起讨论,共同提高。
1.1 Java异常框架总体设计
异常框架的总体结构图
11.1 异常框架总体结构图
如上图所示,java平台框架的异常机制包括程序异常及业务异常。对于程序异常和业务异常我们设计一个BaseException基类,BaseException是一个unchecked exception(即继承于RuntimeException),javaeye上有一篇关于checked exception和unchecked exception的讨论长达几十页,偶在此就不多做解释了,基于此平台框架进行开发的应用可以对BaseException进行派生,例如BizFocus-workflow封装了PersistenceException和ServiceException来对应于持久层异常和业务层异常。持久层和业务层的异常在捕获之后通过log4j输出到了程序日志,然后继续向外抛给展现层的Action控制类,Action不需要对异常进行捕获,因为BizFocus-workflow提供了一个基于webwork的异常拦截器(ExceptionInterceptor)对所有的Action的异常进行拦截。拦截后不同的异常在异常的国际化文件中取得异常提示信息展示给最终用户。
1.2 程序异常的处理
1.2.1异常信息的国际化封装
如图1.1所示,异常信息的国际化封装主要由Messages接口及其实现类MessageImpl和Message_CN.properties、Message.properties两个国际化文件组成。
Messages接口如下:
public interface Messages {
public static final int ERROR_UNKNOWN = 0;
public static final int ERROR_SYSTEM = 1;
public static final int ERROR_WORKFLOW = 2;
public static final int NO_DECISIONVALUE_SET = 3;
………//其它的异常代码
}
MessageImpl实现类的代码如下:
public class MessageImpl implements Messages {
public MessageImpl() {
}
public static String getFormattedMessage(int i, Object aobj[]) {
String s;
try {
s = bundle.getString(String.valueOf(i));
if (aobj != null)
s = MessageFormat.format(s, aobj);
}
catch (MissingResourceException missingresourceexception) {
s = missingresourceexception.getMessage();
}
return s;
}
private static ResourceBundle bundle = ResourceBundle.getBundle("com.xxx.common.MessagesCN");
Message.properties国际化文件如下:
0 = Unknown error
1 = System error
2 = Workflow error
3 = No decision vlaue was set
……其它异常信息
Message_CN.properties文件内容如下:
0 = 未知的错误
1 = 系统错误
2 = 工作流错误
3 = 您没有设置工作流分支节点的决策值
……其它异常信息
1.2.2业务层和持久层异常的国际化处理
对于业务层和持久层异常的处理可以按照如下方式进行:
try {
//业务逻辑代码……
} catch (XXXException ex) {
if (log.isDebugEnabled())//如果是debug状态,直接输出异常的堆栈调用顺序
ex.printStackTrace();
else//不是debug状态,则只输出异常信息到日志文件
log.error(BaseException.getLocalizedMessage(Messages.ERROR_WORKFLOWENGINE, null), ex);
throw new ServiceException(ex);//{zh1}将异常抛给外层
}
1.2.3展现层异常的国际化处理
持久层和业务层的异常首先抛给了展现层的Action,在Action中对Exception不做任何处理直接抛出,例如Action的代码如下:
public String execute() throws Exception {//将Exception直接抛出
//控制逻辑的代码……
}
1.2.4通过webwork拦截器实现异常信息在界面的展示
在上一节中,Action将Exception直接抛出,因此在webwork拦截器中,对Exception进行拦截,将拦截的Exception进行分类,根据分类取得相关的异常提示信息,然后传给统一的异常显示页面,用户在异常页面上就会看到不同的异常信息。ExceptionInterceptor代码示例如下:
public class ExceptionInterceptor implements Interceptor {
private static final Log log = LogFactory.getLog(ExceptionInterceptor.class);
public static final String EXCEPTION = "exception";
public void destroy() {
//To change body of implemented methods use File | Settings | File Templates.
}
public void init() {
//To change body of implemented methods use File | Settings | File Templates.
}
public String intercept(ActionInvocation invocation) throws Exception {
String tipMessge = null;//中文提示信息
String detailMessage = null;//详细的错误信息
try {
return invocation.invoke();
} catch (Exception e) {
if (e instanceof PersistenceException){//判断程序异常的类型
//从国际化文件中取得相应的错误提示信息
tipMessge = ErrorMessage.getLocalizedMessage(Messages.ERROR_PERSISTENCELAYER);
}else if(e instanceof ServiceException){//判断程序异常的类型
tipMessge = ErrorMessage.getLocalizedMessage(Messages.ERROR_SERVICELAYER);
}
//详细的错误信息栈
detailMessage = invocation.getAction();
return EXCEPTION;//webwork将导向EXCEPTION对应的错误信息页面,common/exception.ftl
}
}
}
上述代码是一个比较详细的异常拦截器的代码,业务端可以基于此拦截器进行继承,然后就可以很详细地定制自己在开发过程中的异常。
common/exception.ftl页面的代码如下:
<table align="left" class="common_input_table" style="width:{bfb};height:{bfb}">
<tr>
<td align="center" class="common_td_lable">${action.getText('bizfocus.ui.errorPageTitle')}</td>
</tr>
<tr>
<td align="center" class="common_td_text" >
<div align="center">
<br><br><br><br>
<@ww.if test="errorMessage==null">
` <font color="red">${action.getText('bizfocus.ui.errormessage')}</font>
</@ww.if>
<@ww.else>
<a href=”detailMessage.action?detailMessage=<@ww.property value=’detailMessage’/>”> <font color="red"><@ww.property value="tipMessage"/></font>
</@ww.else>
</div>
</td>
</tr>
</table>
1.3 业务异常的扩展
1.3.1Exception的继承机制
业务异常的处理,是在开发过程中经常遇到的问题,例如工作流引擎提供的根据业务数据自动决策分支节点的路由功能中,如果业务数据没有传递给工作流引擎,则应该抛出一个没有设置决策数据的业务异常(NoDecisionValueSetException),因此在开发业务的过程中,开发者可以根据实际的业务情况进行异常类的定制开发,{zh1}这些业务异常同样被开发者自己扩展的异常拦截器进行拦截,{zh1}根据业务异常的种类将不同的业务异常提示信息展示给最终用户(例如:“您没有设置决策数据给工作流引擎”)。
1.1 Java异常框架总体设计
异常框架的总体结构图
11.1 异常框架总体结构图
如上图所示,java平台框架的异常机制包括程序异常及业务异常。对于程序异常和业务异常我们设计一个BaseException基类,BaseException是一个unchecked exception(即继承于RuntimeException),javaeye上有一篇关于checked exception和unchecked exception的讨论长达几十页,偶在此就不多做解释了,基于此平台框架进行开发的应用可以对BaseException进行派生,例如BizFocus-workflow封装了PersistenceException和ServiceException来对应于持久层异常和业务层异常。持久层和业务层的异常在捕获之后通过log4j输出到了程序日志,然后继续向外抛给展现层的Action控制类,Action不需要对异常进行捕获,因为BizFocus-workflow提供了一个基于webwork的异常拦截器(ExceptionInterceptor)对所有的Action的异常进行拦截。拦截后不同的异常在异常的国际化文件中取得异常提示信息展示给最终用户。
1.2 程序异常的处理
1.2.1异常信息的国际化封装
如图1.1所示,异常信息的国际化封装主要由Messages接口及其实现类MessageImpl和Message_CN.properties、Message.properties两个国际化文件组成。
Messages接口如下:
public interface Messages {
public static final int ERROR_UNKNOWN = 0;
public static final int ERROR_SYSTEM = 1;
public static final int ERROR_WORKFLOW = 2;
public static final int NO_DECISIONVALUE_SET = 3;
………//其它的异常代码
}
MessageImpl实现类的代码如下:
public class MessageImpl implements Messages {
public MessageImpl() {
}
public static String getFormattedMessage(int i, Object aobj[]) {
String s;
try {
s = bundle.getString(String.valueOf(i));
if (aobj != null)
s = MessageFormat.format(s, aobj);
}
catch (MissingResourceException missingresourceexception) {
s = missingresourceexception.getMessage();
}
return s;
}
private static ResourceBundle bundle = ResourceBundle.getBundle("com.xxx.common.MessagesCN");
Message.properties国际化文件如下:
0 = Unknown error
1 = System error
2 = Workflow error
3 = No decision vlaue was set
……其它异常信息
Message_CN.properties文件内容如下:
0 = 未知的错误
1 = 系统错误
2 = 工作流错误
3 = 您没有设置工作流分支节点的决策值
……其它异常信息
1.2.2业务层和持久层异常的国际化处理
对于业务层和持久层异常的处理可以按照如下方式进行:
try {
//业务逻辑代码……
} catch (XXXException ex) {
if (log.isDebugEnabled())//如果是debug状态,直接输出异常的堆栈调用顺序
ex.printStackTrace();
else//不是debug状态,则只输出异常信息到日志文件
log.error(BaseException.getLocalizedMessage(Messages.ERROR_WORKFLOWENGINE, null), ex);
throw new ServiceException(ex);//{zh1}将异常抛给外层
}
1.2.3展现层异常的国际化处理
持久层和业务层的异常首先抛给了展现层的Action,在Action中对Exception不做任何处理直接抛出,例如Action的代码如下:
public String execute() throws Exception {//将Exception直接抛出
//控制逻辑的代码……
}
1.2.4通过webwork拦截器实现异常信息在界面的展示
在上一节中,Action将Exception直接抛出,因此在webwork拦截器中,对Exception进行拦截,将拦截的Exception进行分类,根据分类取得相关的异常提示信息,然后传给统一的异常显示页面,用户在异常页面上就会看到不同的异常信息。ExceptionInterceptor代码示例如下:
public class ExceptionInterceptor implements Interceptor {
private static final Log log = LogFactory.getLog(ExceptionInterceptor.class);
public static final String EXCEPTION = "exception";
public void destroy() {
//To change body of implemented methods use File | Settings | File Templates.
}
public void init() {
//To change body of implemented methods use File | Settings | File Templates.
}
public String intercept(ActionInvocation invocation) throws Exception {
String tipMessge = null;//中文提示信息
String detailMessage = null;//详细的错误信息
try {
return invocation.invoke();
} catch (Exception e) {
if (e instanceof PersistenceException){//判断程序异常的类型
//从国际化文件中取得相应的错误提示信息
tipMessge = ErrorMessage.getLocalizedMessage(Messages.ERROR_PERSISTENCELAYER);
}else if(e instanceof ServiceException){//判断程序异常的类型
tipMessge = ErrorMessage.getLocalizedMessage(Messages.ERROR_SERVICELAYER);
}
//详细的错误信息栈
detailMessage = invocation.getAction();
return EXCEPTION;//webwork将导向EXCEPTION对应的错误信息页面,common/exception.ftl
}
}
}
上述代码是一个比较详细的异常拦截器的代码,业务端可以基于此拦截器进行继承,然后就可以很详细地定制自己在开发过程中的异常。
common/exception.ftl页面的代码如下:
<table align="left" class="common_input_table" style="width:{bfb};height:{bfb}">
<tr>
<td align="center" class="common_td_lable">${action.getText('bizfocus.ui.errorPageTitle')}</td>
</tr>
<tr>
<td align="center" class="common_td_text" >
<div align="center">
<br><br><br><br>
<@ww.if test="errorMessage==null">
` <font color="red">${action.getText('bizfocus.ui.errormessage')}</font>
</@ww.if>
<@ww.else>
<a href=”detailMessage.action?detailMessage=<@ww.property value=’detailMessage’/>”> <font color="red"><@ww.property value="tipMessage"/></font>
</@ww.else>
</div>
</td>
</tr>
</table>
1.3 业务异常的扩展
1.3.1Exception的继承机制
业务异常的处理,是在开发过程中经常遇到的问题,例如工作流引擎提供的根据业务数据自动决策分支节点的路由功能中,如果业务数据没有传递给工作流引擎,则应该抛出一个没有设置决策数据的业务异常(NoDecisionValueSetException),因此在开发业务的过程中,开发者可以根据实际的业务情况进行异常类的定制开发,{zh1}这些业务异常同样被开发者自己扩展的异常拦截器进行拦截,{zh1}根据业务异常的种类将不同的业务异常提示信息展示给最终用户(例如:“您没有设置决策数据给工作流引擎”)。