ejb 异常处理的最佳做法(2)
实体 ejb 组件中的应用程序异常
清单 2 显示了实体 ejb 的一个 ejbcreate() 方法。这个方法的调用者传入一个 orderitemvalue 并请求创建一个 orderitem 实体。因为 orderitemvalue 没有名称,所以抛出了 createexception。
清单 2. 实体 ejb 组件中的样本 ejbcreate() 方法
public integer ejbcreate(orderitemvalue value) throws createexception {
if (value.getitemname() == null) {
throw new createexception("cannot create order without a name");
}
..
..
return null;
}
清单 2 显示了 createexception 的一个很典型的用法。类似地,如果方法的输入参数的值不正确,则查找程序方法将抛出 finderexception。
然而,如果您在使用容器管理的持久性(cmp),则开发者无法控制查找程序方法,从而 finderexception 永远不会被 cmp 实现抛出。尽管如此,在 home 接口的查找程序方法的 throws 子句中声明 finderexception 还是要更好一些。removeexception 是另一个应用程序异常,它在实体被删除时被抛出。
从实体 ejb 组件抛出的应用程序异常基本上限定为这三种类型(createexception、finderexception 和 removeexception)及它们的子类。多数应用程序异常都来源于会话 ejb 组件,因为那里是作出智能决策的地方。实体 ejb 组件一般是哑类,它们的唯一职责就是创建和取回数据。
会话 ejb 组件中的应用程序异常
清单 3 显示了来自会话 ejb 组件的一个方法。这个方法的调用者设法订购 n 件某特定类型的某商品。sessionejb() 方法计算出仓库中的数量不够,于是抛出 notenoughstockexception。notenoughstockexception 适用于特定于业务的场合;当抛出了这个异常时,调用者会得到采用另一个备用途径的建议,让他订购更少数量的商品。
清单 3. 会话 ejb 组件中的样本容器回调方法
public itemvalueobject[] placeorder(int n, itemtype itemtype) throws
notenoughstockexception {
//check inventory.
collection orders = itemhome.findbyitemtype(itemtype);
if (orders.size() < n) {
throw notenoughstockexception("insufficient stock for " + itemtype);
}
}
处理系统异常
系统异常处理是比应用程序异常处理更为复杂的论题。由于会话 ejb 组件和实体 ejb 组件处理系统异常的方式相似,所以,对于本部分的所有示例,我们都将着重于实体 ejb 组件,不过请记住,其中的大部分示例也适用于处理会话 ejb 组件。
当引用其它 ejb 远程接口时,实体 ejb 组件会碰到 remoteexception,而查找其它 ejb 组件时,则会碰到 namingexception,如果使用 bean 管理的持久性(bmp),则会碰到 sqlexception。与这些类似的受查系统异常应该被捕获并作为 ejbexception 或它的一个子类抛出。原始的异常应被包装起来。清单 4 显示了一种处理系统异常的办法,这种办法与处理系统异常的 ejb 容器的行为一致。通过包装原始的异常并在实体 ejb 组件中将它重新抛出,您就确保了能够在想记录它的时候访问该异常。
清单 4. 处理系统异常的一种常见方式
try {
orderhome orderhome = ejbhomefactory.getinstance().getorderhome();
order order = orderhome.findbyprimarykey(integer id);
} catch (namingexception ne) {
throw new ejbexception(ne);
} catch (sqlexception se) {
throw new ejbexception(se);
} catch (remoteexception re) {
throw new ejbexception(re);
}
避免重复记录
通常,异常记录发生在会话 ejb 组件中。但如果直接从 ejb 层外部访问实体 ejb 组件,又会怎么样呢?要是这样,您就不得不在实体 ejb 组件中记录异常并抛出它。这里的问题是,调用者没办法知道异常是否已经被记录,因而很可能再次记录它,从而导致重复记录。更重要的是,调用者没办法访问初始记录时所生成的唯一的标识。任何没有交叉引用机制的记录都是毫无用处的。
请考虑这种最糟糕的情形:单机 java 应用程序访问了实体 ejb 组件中的一个方法 foo()。在一个名为 bar() 的会话 ejb 方法中也访问了同一个方法。一个 web 层客户机调用会话 ejb 组件的方法 bar() 并也记录了该异常。如果当从 web 层调用会话 ejb 方法 bar() 时在实体 ejb 方法 foo() 中发生了一个异常,则该异常将被记录到三个地方:先是在实体 ejb 组件,然后是在会话 ejb 组件,最后是在 web 层。而且,没有一个堆栈跟踪可以被交叉引用!
幸运的是,解决这些问题用常规办法就可以很容易地做到。您所需要的只是一种机制,使调用者能够:
访问唯一的标识
查明异常是否已经被记录了
您可以派生 ejbexception 的子类来存储这样的信息。清单 5 显示了 loggableejbexception 子类:
清单 5. loggableejbexception ? ejbexception 的一个子类
public class loggableejbexception extends ejbexception {
protected boolean islogged;
protected string uniqueid;
public loggableejbexception(exception exc) {
super(exc);
islogged = false;
uniqueid = exceptionidgenerator.getexceptionid();
}
..
..
}
类 loggableejbexception 有一个指示符标志(islogged),用于检查异常是否已经被记录了。每当捕获一个 loggableejbexception 时,看一下该异常是否已经被记录了(islogged == false)。如果 islogged 为 false,则记录该异常并把标志设置为 true。
exceptionidgenerator 类用当前时间和机器的主机名为异常生成唯一的标识。如果您喜欢,也可以用有想象力的算法来生成这个唯一的标识。如果您在实体 ejb 组件中记录了异常,则这个异常将不会在别的地方被记录。如果您没有记录就在实体 ejb 组件中抛出了 loggableejbexception,则这个异常将被记录到会话 ejb 组件中,但不记录到 web 层中。
清单 6 显示了使用这一技术重写后的清单 4。您还可以继承 loggableexception 以适合于您的需要(通过给异常指定错误代码等)。
清单 6. 使用 loggableejbexception 的异常处理
try {
orderhome orderhome = ejbhomefactory.getinstance().getorderhome();
order order = orderhome.findbyprimarykey(integer id);
} catch (namingexception ne) {
throw new loggableejbexception(ne);
} catch (sqlexception se) {
throw new loggableejbexception(se);
} catch (remoteexception re) {
throwable t = re.detail;
if (t != null && t instanceof exception) {
throw new loggableejbexception((exception) re.detail);
} else {
throw new loggableejbexception(re);
}
}
记录 remoteexception
从清单 6 中,您可以看到 naming 和 sql 异常在被抛出前被包装到了 loggableejbexception 中。但 remoteexception 是以一种稍有不同 ? 而且要稍微花点气力 ? 的方式处理的。 会话 ejb 组件中的系统异常
如果您决定记录会话 ejb 异常,请使用清单 7 所示的记录代码;否则,请抛出异常,如清单 6 所示。您应该注意到,会话 ejb 组件处理异常可有一种与实体 ejb 组件不同的方式:因为大多数 ejb 系统都只能从 web 层访问,而且会话 ejb 可以作为 ejb 层的虚包,所以,把会话 ejb 异常的记录推迟到 web 层实际上是有可能做到的。
它之所以不同,是因为在 remoteexception 中,实际的异常将被存储到一个称为 detail(它是 throwable 类型的)的公共属性中。在大多数情况下,这个公共属性保存有一个异常。如果您调用 remoteexception 的 printstacktrace,则除打印 detail 的堆栈跟踪之外,它还会打印异常本身的堆栈跟踪。您不需要像这样的 remoteexception 的堆栈跟踪。
为了把您的应用程序代码从错综复杂的代码(例如 remoteexception 的代码)中分离出来,这些行被重新构造成一个称为 exceptionlogutil 的类。有了这个类,您所要做的只是每当需要创建 loggableejbexception 时调用 exceptionlogutil.createloggableejbexception(e)。请注意,在清单 6 中,实体 ejb 组件并没有记录异常;不过,即便您决定在实体 ejb 组件中记录异常,这个解决方案仍然行得通。清单 7 显示了实体 ejb 组件中的异常记录:
清单 7. 实体 ejb 组件中的异常记录
try {
orderhome orderhome = ejbhomefactory.getinstance().getorderhome();
order order = orderhome.findbyprimarykey(integer id);
} catch (remoteexception re) {
loggableejbexception le =
exceptionlogutil.createloggableejbexception(re);
string tracestr = stacktraceutil.getstacktrace(le);
category.getinstance(getclass().getname()).error(le.getuniqueid() +
":" + tracestr);
le.setlogged(true);
throw le;
}
您在清单 7 中看到的是一个非常简单明了的异常记录机制。一旦捕获受查系统异常就创建
闽公网安备 35060202000074号