在一个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);//
最后将异常抛给外层
}
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
页面的代码如下:
${action.getText('bizfocus.ui.errorPageTitle')} |
<@ww.if test="errorMessage==null">
` ${action.getText('bizfocus.ui.errormessage')}
<@ww.else>
|
1.3
业务异常的扩展
1.3.1Exception
的继承机制
业务异常的处理,是在开发过程中经常遇到的问题,例如工作流引擎提供的根据业务数据自动决策分支节点的路由功能中,如果业务数据没有传递给工作流引擎,则应该抛出一个没有设置决策数据的业务异常
(NoDecisionValueSetException)
,因此在开发业务的过程中,开发者可以根据实际的业务情况进行异常类的定制开发,最后这些业务异常同样被开发者自己扩展的异常拦截器进行拦截,最后根据业务异常的种类将不同的业务异常提示信息展示给最终用户(例如:
“
您没有设置决策数据给工作流引擎
”
)。