随着 J2EE 成ؓ(f)企业开发^C选,来多Z J2EE 的应用程序将投入生。J2EE q_的重要组件之一?Enterprise JavaBeanQEJBQAPI。J2EE ?EJB 技术一h供了许多优点Q但随之而来的还有一些新的挑战。特别是企业pȝQ其中的M问题都必d速得到解冟뀂在本文中,企业 Java ~程老手 Srikanth Shenoy 展现了他?EJB 异常处理斚w的最?jng)_法,q些做法可以更快解决问题?/BLOCKQUOTE>
?hello-world 情Ş中,异常处理非常单。每当碰到某个方法的异常Ӟ捕莯异常q打印堆栈跟t或者声明这个方法抛出异常。不q的是,q种办法不以处理现实中出现的各U类型的异常。在生pȝ中,当有异常抛出Ӟ很可能是最l用h法处理他或她的请求。当发生q样的异常时Q最l用户通常希望能这P(x)
- 有一条清楚的消息表明已经发生了一个错?
- 有一个唯一的错误号Q他可以据此讉K可方便获得的客户支持pȝ
- 问题快速得到解冻Iq且可以信他的h已经得到处理Q或者将在设定的旉D内得到处理
理想情况下,企业U系l将不仅为客h供这些基本的服务Q还准备好一些必要的后端机制。D例来_(d)客户服务组应该收到x的错误通知Q以便在客户打电(sh)话求助之前服务代表就能意识到问题。此外,服务代表应该能够交叉引用用户的唯一错误号和产品日志Q从而快速识别问??最好是能把问题定位到确切的行号或确切的Ҏ(gu)。ؓ(f)了给最l用户和支持组提供他们需要的工具和服务,在构Z个系l时Q?zhn)必dpȝ被部|后可能出问题的所有地方心中有数?/P>
在本文中Q我们将谈谈Z EJB 的系l中的异常处理。我们将从回ּ常处理的基础知识开始,包括日志实用E序的用,然后Q很快就转入?EJB 技术如何定义和理不同cd的异常进行更详细的讨论。此后,我们通过一些代码示例来研究一些常见的异常处理解决Ҏ(gu)的优~点Q我q将展示我自己在充分利用 EJB 异常处理斚w的最?jng)_法?/P>
h意,本文假设(zhn)熟(zhn)?J2EE ?EJB 技术。?zhn)应理解实?bean 和会(x)?bean 的差异。如果?zhn)?bean 理的持久性(bean-managed persistenceQBMPQ)(j)和容器管理的持久性(container-managed persistenceQCMPQ)(j)在实?bean 上下文中是什么意思稍有了解,也是有帮助的。请参阅 参考资?/FONT>部分了解关于 J2EE ?EJB 技术的更多信息?
异常处理基础知识
解决pȝ错误的第一步是建立一个与生pȝh相同构造的试pȝQ然后跟t导致抛出异常的所有代码,以及(qing)代码中的所有不同分支。在分布式应用程序中Q很可能是调试器不工作了Q所以,(zhn)可能将?System.out.println()
Ҏ(gu)跟踪异常?System.out.println
管很方便,但开销巨大。在盘 I/O 期间Q?System.out.println
?I/O 处理q行同步Q这极大降低了吞吐量。在~省情况下,堆栈跟踪被记录到控制台。但是,在生产系l中Q浏览控制台以查看异常跟t是行不通的。而且Q不能保证堆栈跟t会(x)昄在生产系l中Q因为,?NT 上,pȝ理员可以把 System.out
?System.err
映射?' '
Q在 UNIX 上,可以映射?dev/null
。此外,如果(zhn)把 J2EE 应用E序服务器作?NT 服务q行Q甚至不?x)有控制台。即使?zhn)把控制台日志重定向到一个输出文Ӟ当?J2EE 应用E序服务器重新启动时Q这个文件很可能也将被重写?
 |
异常处理的原?/B>
以下是一些普遍接受的异常处理原则Q?
- 如果无法处理某个异常Q那׃要捕获它?
- 如果捕获了一个异常,请不要胡乱处理它?
- 量在靠q异常被抛出的地Ҏ(gu)获异常?
- 在捕获异常的地方它记录到日志中Q除非?zhn)打算它重新抛出?
- 按照(zhn)的异常处理必须多精l来构造?zhn)的方法?
- 需要用几种cd的异常就用几U,其是对于应用程序异常?
W?1 Ҏ(gu)然与W?3 点相抵触。实际的解决Ҏ(gu)是以下两者的折衷Q?zhn)在距异常被抛出多q的地方它捕获Q在完全丢失原始异常的意图或内容之前Q?zhn)可以让异常落在多q的地方?
?/B>Q尽这些原则的应用遍及(qing)所?EJB 异常处理机制Q但它们q不是特别针?EJB 异常处理的? | |
׃以上q些原因Q把代码l装成品ƈ同时包含 System.out.println
q不是一U选择。在试期间使用 System.out.println
Q然后在形成产品之前除去 System.out.println
也不是上{,因ؓ(f)q样做意味着(zhn)的产品代码与测试代码运行得不尽相同。?zhn)需要的是一U声明控制日志机Ӟ以(zhn)的试代码和品代码相同,q且当记录日志以声明方式关闭Ӟl品带来的性能开销最?
q里的解x案显然是使用一个日志实用程序。采用恰当的~码U定Q日志实用程序将负责_地记录下Mcd的消息,不论是系l错误还是一些警告。所以,我们在q一步讲qC前谈谈日志实用程序?/P>
日志领域Q鸟?/FONT>
每个大型应用E序在开发、测试及(qing)产品周期中都使用日志实用E序。在今天的日志领域中Q有几个角逐者,其中有两个广Zh知。一个是 Log4JQ它是来?Apache ?Jakarta 的一个开放源代码的项目。另一个是 J2SE 1.4 捆绑提供的,它是最q刚加入到这个行列的。我们将使用 Log4J 说明本文所讨论的最?jng)_法;但是Q这些最?jng)_法ƈ不特别依赖于 Log4J?/P>
Log4J 有三个主要组Ӟ(x)layout、appender ?category?Layou代表消息被记录到日志中的格式?appender是消息将被记录到的物理位|的别名。?category则是有名U的实体Q?zhn)可以把它当作是日志的句柄。layout ?appender ?XML 配置文g中声明。每?category 带有它自q layout ?appender 定义。当(zhn)获取了一?category q把消息记录到它那里Ӟ消息在与?category 相关联的各个 appender 处结束,q且所有这些消息都以 XML 配置文g中指定的 layout 格式表示?
Log4J l消息指定四U优先Q它们是 ERROR、WARN、INFO ?DEBUG。ؓ(f)便于本文的讨论,所有异帔R以具?ERROR 优先U记录。当记录本文中的一个异常时Q我们将能够扑ֈ获取 categoryQ?Category.getInstance(String name)
Ҏ(gu)Q的代码Q然后调用方?category.error()
Q它与具?ERROR 优先U的消息相对应)(j)?
管日志实用E序能帮助我们把消息记录到适当的持久位|,但它们ƈ不能栚w问题。它们不能从产品日志中精找出某个客L(fng)问题报告Q这一便利技术留l?zhn)把它构徏到(zhn)正在开发的pȝ中?/P>
要了解关?Log4J 日志实用E序?J2SE 所带的日志实用E序的更多信息,请参?参考资?/FONT>部分?
异常的类?/FONT>
异常的分cL不同方式。这里,我们讨Z EJB 的角度如何对异常q行分类。EJB 规范?yu)异常大致分成三c:(x)
- JVM 异常Q这U类型的异常?JVM 抛出?
OutOfMemoryError
是 JVM 异常的一个常见示例。对 JVM 异常(zhn)无能ؓ(f)力。它们表明一U致命的情况。唯一得体的退出办法是停止应用E序服务器(可能要增加硬件资源)(j)Q然后重新启动系l?
- 应用E序异常Q应用程序异常是一U定制异常,由应用程序或W三方的库抛出。这些本质上是受查异常(checked exceptionQ;它们预示了业务逻辑中的某个条g未满。在q样的情况下QEJB Ҏ(gu)的调用者可以得体地处理q种局面ƈ采用另一条备用途径?
- pȝ异常Q在大多数情况下Q系l异常由 JVM 作ؓ(f)
RuntimeException
的子cL出。例如, NullPointerException
?ArrayOutOfBoundsException
因代码中的错误而被抛出。另一U类型的pȝ异常在系l碰到配|不当的资源Q例如,拼写错误?JNDI 查找QJNDI lookupQ)(j)时发生。在q种情况下,pȝ将抛出一个受查异常。捕莯些受查系l异常ƈ它们作为非受查异常Qunchecked exceptionQ抛出颇有意义。最重要的规则是Q如果?zhn)?gu)个异常无能ؓ(f)力,那么它就是一个系l异常ƈ且应当作为非受查异常抛出?
?/B>Q?受查异常是一个作?java.lang.Exception
的子cȝ Java cR通过?java.lang.Exception
z子类Q就强制(zhn)在~译时捕莯个异常。相反地Q?非受查异?/I>则是一个作?java.lang.RuntimeException
的子cȝ Java cR从 java.lang.RuntimeException
z子类保了编译器不会(x)强制(zhn)捕莯个异常?
EJB 容器怎样处理异常
EJB 容器拦截 EJB lg上的每一个方法调用。结果,Ҏ(gu)调用中发生的每一个异怹?EJB 容器拦截到。EJB 规范只处理两U类型的异常Q应用程序异常和pȝ异常?/P>
EJB 规范?应用E序异常定义为在q程接口中的Ҏ(gu)说明上声明的M异常Q而不?RemoteException
Q。应用程序异常是业务工作中的一U特D情形。当q种cd的异常被抛出Ӟ客户Z(x)得到一个恢复选项Q这个选项通常是要求以一U不同的方式处理h。不q,qƈ不意味着M在远E接口方法的 throws
子句中声明的非受查异帔R?x)被当作应用E序异常对待。EJB 规范明确指出Q应用程序异怸应?RuntimeException
或它的子cR?
当发生应用程序异常时Q除非被昑ּ要求Q通过调用兌?EJBContext
对象?setRollbackOnly()
Ҏ(gu)Q回滚事务,否则 EJB 容器׃?x)这样做。事实上Q应用程序异常被保证以它原本的状态传送给客户机:(x)EJB 容器l不?x)以M方式包装或修改异常?
pȝ异常被定义ؓ(f)受查异常或非受查异常QEJB Ҏ(gu)不能从这U异常恢复。当 EJB 容器拦截到非受查异常Ӟ它会(x)回滚事务q执行Q何必要的清理工作。接着Q它把该非受查异常包装到 RemoteException
中,然后抛给客户机。这PEJB 容器把所有非受查异常作ؓ(f) RemoteException
Q或者作为其子类Q例?TransactionRolledbackException
Q提供给客户机?
对于受查异常的情况,容器q不?x)自动执行上面所描述的内务处理。要使用 EJB 容器的内部内务处理,(zhn)将必须把受查异怽为非受查异常抛出。每当发生受查系l异常(?NamingException
Q时Q?zhn)都应该通过包装原始的异常抛?javax.ejb.EJBException
或其子类。因?EJBException
本n是非受查异常Q所以不需要在Ҏ(gu)?throws
子句中声明它。EJB 容器捕获 EJBException
或其子类Q把它包装到 RemoteException
中,然后?RemoteException
抛给客户机?
虽然pȝ异常由应用程序服务器记录Q这?EJB 规范规定的)(j)Q但记录格式因应用E序服务器的不同而异。ؓ(f)了访问所需的统计信息,企业常常需要对所生成的日志运?shell/Perl 脚本。ؓ(f)了确保记录格式的l一Q在(zhn)的代码中记录异怼(x)更好些?/P>
?/B>QEJB 1.0 规范要求把受查系l异怽?RemoteException
抛出。从 EJB 1.1 规范赯?EJB 实现cȝ不应抛出 RemoteException
?
常见的异常处理策?/FONT>
如果没有异常处理{略Q项目小l的不同开发者很可能?x)编写以不同方式处理异常的代码。由于同一个异常在pȝ的不同地方可能以不同的方式被描述和处理,所以,q至会(x)使品支持小l感到迷惑。缺乏策略还?x)导致在整个pȝ的多个地斚w有记录。日志应该集中v来或者分成几个可理的单元。理想的情况是,应在可能少的地方记录异常日志,同时不损失内宏V在q一部分?qing)其后的几个部分Q我展C可以在整个企业pȝ中以l一的方式实现的~码{略。?zhn)可以?参考资?/FONT>部分下蝲本文开发的实用E序cR?
清单 1 昄了来自会(x)?EJB lg的一个方法。这个方法删除某个客户在特定日期前所下的全部订单。首先,它获?OrderEJB
?Home 接口。接着Q它取回某个特定客户的所有订单。当它碰到在某个特定日期之前所下的订单Ӟ删除所订购的商品,然后删除订单本n。请注意Q抛Z三个异常Q显CZ三种常见的异常处理做法。(为简单v见,假设~译器优化未被用。)(j)
清单 1. 三种常见的异常处理做?/B>
100 try {
101 OrderHome homeObj = EJBHomeFactory.getInstance().getOrderHome();
102 Collection orderCollection = homeObj.findByCustomerId(id);
103 iterator orderItter = orderCollection.iterator();
104 while (orderIter.hasNext()) {
105 Order orderRemote = (OrderRemote) orderIter.getNext();
106 OrderValue orderVal = orderRemote.getValue();
107 if (orderVal.getDate() < "mm/dd/yyyy") {
108 OrderItemHome itemHome =
EJBHomeFactory.getInstance().getItemHome();
109 Collection itemCol = itemHome.findByOrderId(orderId)
110 Iterator itemIter = itemCol.iterator();
111 while (itemIter.hasNext()) {
112 OrderItem item = (OrderItem) itemIter.getNext();
113 item.remove();
114 }
115 orderRemote.remove();
116 }
117 }
118 } catch (NamingException ne) {
119 throw new EJBException("Naming Exception occurred");
120 } catch (FinderException fe) {
121 fe.printStackTrace();
122 throw new EJBException("Finder Exception occurred");
123 } catch (RemoteException re) {
124 re.printStackTrace();
125 //Some code to log the message
126 throw new EJBException(re);
127 }
|
现在Q让我们用上面所C的代码来研I一下所展示的三U异常处理做法的~点?/P>
抛出Q重抛出带有出错消息的异?/B>
NamingException
可能发生在行 101 或行 108。当发生 NamingException
Ӟq个Ҏ(gu)的调用者就得到 RemoteException
q向后跟t该异常到行 119。调用者ƈ不能告知 NamingException
实际是发生在?101 q是?108。由于异常内容要直到被记录了才能得到保护Q所以,q个问题的根源很难查出。在q种情Ş下,我们p异常的内容被“吞掉”了。正如这个示例所C,抛出或重抛出一个带有消息的异常q不是一U好的异常处理解军_法?
记录到控制台q抛Z个异?/B>
FinderException
可能发生在行 102 ?109。不q,׃异常被记录到控制収ͼ所以仅当控制台可用时调用者才能向后跟t到?102 ?109。这昄不可行,所以异常只能被向后跟踪到行 122。这里的推理同上?
包装原始的异总保护其内?/B>
RemoteException
可能发生在行 102?06?09?13 ?115。它在行 123 ?catch
块被捕获。接着Q这个异常被包装?EJBException
中,所以,不论调用者在哪里记录它,它都能保持完整。这U办法比前面两种办法更好Q同时演CZ没有日志{略的情c(din)如?deleteOldOrders()
Ҏ(gu)的调用者记录该异常Q那么将D重复记录。而且Q尽有了日志记录,但当客户报告某个问题Ӟ产品日志或控制台q不能被交叉引用?
EJB 异常处理探试?/FONT>
EJB lg应抛出哪些异常?(zhn)应它们记录到pȝ中的什么地方?q两个问题盘栚wl、相互联p,应该一赯冟뀂解军_法取决于以下因素Q?/P>
- (zhn)的 EJB pȝ设计Q在良好?EJB 设计中,客户机绝不调用实?EJB lg上的Ҏ(gu)。多数实?EJB Ҏ(gu)调用发生在会(x)?EJB lg中。如果?zhn)的设计遵循这些准则,则(zhn)应该用?x)?EJB lg来记录异常。如果客h直接调用了实?EJB Ҏ(gu)Q则(zhn)还应该把消息记录到实体 EJB lg中。然而,存在一个难题:(x)相同的实?EJB Ҏ(gu)可能也会(x)被会(x)?EJB lg调用。在q种情Ş下,如何避免重复记录呢?cM圎ͼ当一个会(x)?EJB lg调用其它实体 EJB Ҏ(gu)Ӟ(zhn)如何避免重复记录呢Q很快我们就探讨一U处理这两种情况的通用解决Ҏ(gu)。(h意,EJB 1.1 q未从体pȝ构上L客户用实?EJB lg上的Ҏ(gu)。在 EJB 2.0 中,(zhn)可以通过为实?EJB lg定义本地接口规定q种限制。)(j)
- 计划的代码重用范?/B>Q这里的问题是?zhn)是打把日志代码d到多个地方,q是打算重新设计、重新构造代码来减少日志代码?
- (zhn)要Z服务的客h的类?/B>Q考虑(zhn)是ؓ(f) J2EE Web 层、单?Java 应用E序、PDA q是ؓ(f)其它客户机服务是很重要的。Web 层设计有各种形状和大。如果?zhn)在用命令(CommandQ模式,在这个模式中QW(xu)eb 层通过每次传入一个不同的命o(h)调用 EJB 层中的相同方法,那么Q把异常记录到命令在其中执行?EJB lg中是很有用的。在多数其它?Web 层设计中Q把异常记录?Web 层本w要更容易,也更好,因ؓ(f)(zhn)需要把异常日志代码d到更的地方。如果?zhn)?Web 层和 EJB 层在同一地方q且不需要支持Q何其它类型的客户机,那么应该考虑后一U选择?
- (zhn)将处理的异常的cdQ应用程序或pȝQ?/B>Q处理应用程序异怸处理pȝ异常有很大不同。系l异常的发生不受 EJB 开发者意囄控制。因为系l异常的含义不清楚,所以内容应指明异常的上下文。?zhn)已经看到了,通过对原始异常进行包装ɘq个问题得到了最好的处理。另一斚wQ应用程序异常是?EJB 开发者显式抛出的Q通常包装有一条消息。因为应用程序异常的含义清楚Q所以没有理p保护它的上下文。这U类型的异常不必记录?EJB 层或客户机层Q它应该以一U有意义的方式提供给最l用P带上指向所提供的解x案的另一条备用途径。系l异常消息没必要Ҏ(gu)l用户很有意义?
 |
处理应用E序异常
在这一部分?qing)其后的几个部分中,我们更仔细地研I用 EJB 异常处理应用E序异常和系l异常,以及(qing) Web 层设计。作个讨论的一部分Q我们将探讨处理从会(x)话和实体 EJB lg抛出的异常的不同方式?/P>
实体 EJB lg中的应用E序异常
清单 2 昄了实?EJB 的一?ejbCreate()
Ҏ(gu)。这个方法的调用者传入一?OrderItemValue
q请求创Z?OrderItem
实体。因?OrderItemValue
没有名称Q所以抛Z CreateException
?
清单 2. 实体 EJB lg中的h ejbCreate() Ҏ(gu)
public Integer ejbCreate(OrderItemValue value) throws CreateException {
if (value.getItemName() == null) {
throw new CreateException("Cannot create Order without a name");
}
..
..
return null;
}
|
清单 2 昄?CreateException
的一个很典型的用法。类似地Q如果方法的输入参数的g正确Q则查找E序Ҏ(gu)抛?FinderException
?
然而,如果(zhn)在使用容器理的持久性(CMPQ,则开发者无法控制查扄序方法,从?FinderException
永远不会(x)?CMP 实现抛出。尽如此,?Home 接口的查扄序方法的 throws
子句中声?FinderException
q是要更好一些?RemoveException
是另一个应用程序异常,它在实体被删除时被抛出?
从实?EJB lg抛出的应用程序异常基本上限定三种cdQ?CreateException
?FinderException
?RemoveException
Q及(qing)它们的子cR多数应用程序异帔R来源于会(x)?EJB lgQ因为那里是作出决策的地斏V实?EJB lg一般是哑类Q它们的唯一职责是创徏和取回数据?
?x)?EJB lg中的应用E序异常
清单 3 昄了来自会(x)?EJB lg的一个方法。这个方法的调用者设法订?n 件某特定cd的某商品?SessionEJB()
Ҏ(gu)计算Z库中的数量不够,于是抛出 NotEnoughStockException
?NotEnoughStockException
适用于特定于业务的场合;当抛Zq个异常Ӟ调用者会(x)得到采用另一个备用途径的徏议,让他订购更少数量的商品?
清单 3. ?x)?EJB lg中的h容器回调Ҏ(gu)
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);
}
}
|
处理pȝ异常
pȝ异常处理是比应用E序异常处理更ؓ(f)复杂的论题。由于会(x)?EJB lg和实?EJB lg处理pȝ异常的方式相|所以,对于本部分的所有示例,我们都将着重于实体 EJB lgQ不q请CQ其中的大部分示例也适用于处理会(x)?EJB lg?/P>
当引用其?EJB q程接口Ӟ实体 EJB lg?x)碰?RemoteException
Q而查扑օ?EJB lgӞ则会(x)到 NamingException
Q如果?bean 理的持久性(BMPQ,则会(x)到 SQLException
。与q些cM的受查系l异常应该被捕获q作?EJBException
或它的一个子cL出。原始的异常应被包装h。清?4 昄了一U处理系l异常的办法Q这U办法与处理pȝ异常?EJB 容器的行Z致。通过包装原始的异常ƈ在实?EJB lg中将它重新抛出,(zhn)就保了能够在惌录它的时候访问该异常?
清单 4. 处理pȝ异常的一U常见方?/B>
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);
}
|
避免重复记录
通常Q异常记录发生在?x)?EJB lg中。但如果直接?EJB 层外部访问实?EJB lgQ又?x)怎么样呢Q要是这P(zhn)就不得不在实体 EJB lg中记录异常ƈ抛出它。这里的问题是,调用者没办法知道异常是否已经被记录,因而很可能再次记录它,从而导致重复记录。更重要的是Q调用者没办法讉K初始记录时所生成的唯一的标识。Q何没有交叉引用机制的记录都是毫无用处的?/P>
误(g)虑q种最p糕的情形:(x)单机 Java 应用E序讉K了实?EJB lg中的一个方?foo()
。在一个名?bar()
的会(x)?EJB Ҏ(gu)中也讉K了同一个方法。一?Web 层客h调用?x)?EJB lg的方?bar()
q也记录了该异常。如果当?Web 层调用会(x)?EJB Ҏ(gu) bar()
时在实体 EJB Ҏ(gu) foo()
中发生了一个异常,则该异常被记录C个地方:(x)先是在实?EJB lgQ然后是在会(x)?EJB lgQ最后是?Web 层。而且Q没有一个堆栈跟t可以被交叉引用Q?
q运的是Q解册些问题用常规办法可以很Ҏ(gu)地做到。?zhn)所需要的只是一U机Ӟ使调用者能够:(x)
(zhn)可以派?EJBException
的子cL存储q样的信息。清?5 昄?LoggableEJBException
子类Q?
清单 5. LoggableEJBException ?EJBException 的一个子c?/B>
public class LoggableEJBException extends EJBException {
protected boolean isLogged;
protected String uniqueID;
public LoggableEJBException(Exception exc) {
super(exc);
isLogged = false;
uniqueID = ExceptionIDGenerator.getExceptionID();
}
..
..
}
|
c?LoggableEJBException
有一个指C符标志Q?isLogged
Q,用于(g)查异常是否已l被记录了。每当捕获一?LoggableEJBException
Ӟ看一下该异常是否已经被记录了Q?isLogged == false
Q。如?isLogged ?falseQ则记录该异常ƈ把标志设|ؓ(f) true
?
ExceptionIDGenerator
cȝ当前旉和机器的L名ؓ(f)异常生成唯一的标识。如果?zhn)喜欢Q也可以用有惌力的法来生成这个唯一的标识。如果?zhn)在实?EJB lg中记录了异常Q则q个异常不?x)在别的地方被记录。如果?zhn)没有记录在实?EJB lg中抛Z LoggableEJBException
Q则q个异常被记录C(x)?EJB lg中,但不记录?Web 层中?
?6 昄了用这一技术重写后的清?4。?zhn)q可以?LoggableException
以适合于?zhn)的需要(通过l异常指定错误代码等Q?
清单 6. 使用 LoggableEJBException 的异常处?/B>
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 中,(zhn)可以看?naming ?SQL 异常在被抛出前被包装C LoggableEJBException
中。但 RemoteException
是以一U稍有不??而且要稍微花Ҏ(gu)??的方式处理的?
 |
?x)?EJB lg中的pȝ异常
如果(zhn)决定记录会(x)?EJB 异常Q请使用 清单 7所C的记录代码Q否则,h出异常,?清单 6所C。?zhn)应该注意刎ͼ会(x)?EJB lg处理异常可有一U与实体 EJB lg不同的方式:(x)因ؓ(f)大多?EJB pȝ都只能从 Web 层访问,而且?x)?EJB 可以作ؓ(f) EJB 层的虚包Q所以,把会(x)?EJB 异常的记录推q到 Web 层实际上是有可能做到的? | |
它之所以不同,是因为在 RemoteException
中,实际的异常将被存储到一个称?detail
Q它?Throwable
cd的)(j)的公共属性中。在大多数情况下Q这个公共属性保存有一个异常。如果?zhn)调?RemoteException
?printStackTrace
Q则除打?detail 的堆栈跟t之外,它还?x)打印异常本w的堆栈跟踪。?zhn)不需要像q样?RemoteException
的堆栈跟t?
Z把?zhn)的应用程序代码从错综复杂的代码(例?RemoteException
的代码)(j)中分d来,q些行被重新构造成一个称?ExceptionLogUtil
的类。有了这个类Q?zhn)所要做的只是每当需要创?LoggableEJBException
时调?ExceptionLogUtil.createLoggableEJBException(e)
。请注意Q在清单 6 中,实体 EJB lgq没有记录异常;不过Q即便?zhn)军_在实?EJB lg中记录异常,q个解决Ҏ(gu)仍然行得通。清?7 昄了实?EJB lg中的异常记录Q?
清单 7. 实体 EJB lg中的异常记录
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;
}
|
(zhn)在清单 7 中看到的是一个非常简单明了的异常记录机制。一旦捕获受查系l异常就创徏一个新?LoggableEJBException
。接着Q用类 StackTraceUtil
获取 LoggableEJBException 的堆栈跟t,把它作ؓ(f)一个字W串。然后,使用 Log4J category 把该字符串作Z个错误加以记录?
StackTraceUtil cȝ工作原理
在清?7 中,(zhn)看C一个新的称?StackTraceUtil
的类。因?Log4J 只能记录 String
消息Q所以这个类负责解决把堆栈跟t{换成 String
的问题。清?8 说明?StackTraceUtil
cȝ工作原理Q?
清单 8. StackTraceUtil c?/B>
public class StackTraceUtil {
public static String getStackTrace(Exception e)
{
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
return sw.toString();
}
..
..
}
|
java.lang.Throwable
中缺省的 printStackTrace()
Ҏ(gu)把出错消息记录到 System.err
?Throwable
q有一个重载的 printStackTrace()
Ҏ(gu)Q它把出错消息记录到 PrintWriter
?PrintStream
。上面的 StackTraceUtil
中的Ҏ(gu)?StringWriter
包装?PrintWriter
中。当 PrintWriter
包含有堆栈跟t时Q它只是调用 StringWriter
?toString()
Q以获取该堆栈跟t的 String
表示?
Web 层的 EJB 异常处理
?Web 层设计中Q把异常记录机制攑ֈ客户机端往往更容易也更高效。要能做到这一点,W(xu)eb 层就必须?EJB 层的唯一客户机。此外,W(xu)eb 层必d立在以下模式或框架之一的基上:(x)
- 模式Q业务委z(Business DelegateQ、FrontController 或拦截过滤器QIntercepting FilterQ?
- 框架QStruts 或Q何包含层ơ结构的cM?MVC 框架的框?
Z么异常记录应该在客户机端上发生呢Q嗯Q首先,控制未传到应用E序服务器之外。所谓的客户机层?J2EE 应用E序服务器本w上q行Q它?JSP c(din)servlet 或它们的助手cȝ成。其ơ,在设计良好的 Web 层中的类有一个层ơ结构(例如Q在业务委派QBusiness DelegateQ类、拦截过滤器QIntercepting FilterQ类、http h处理E序Qhttp request handlerQ类?JSP 基类QJSP base classQ中Q或者在 Struts Action cMQ,或?FrontController servlet 形式的单点调用。这些层ơ结构的基类或?Controller cM的中央点可能包含有异常记录代码。对于基于会(x)?EJB 记录的情况,EJB lg中的每一个方法都必须h记录代码。随着业务逻辑的增加,?x)?EJB Ҏ(gu)的数量也?x)增加,记录代码的数量也会(x)增加。Web 层系l将需要更的记录代码。如果?zhn)?Web 层和 EJB 层在同一地方q且不需要支持Q何其它类型的客户机,那么(zhn)应该考虑q一备用Ҏ(gu)。不怎样Q记录机制不?x)改变;?zhn)可以用与前面的部分所描述的相同技术?/P>
真实世界的复杂?/FONT>
到现在ؓ(f)止,(zhn)已l看C单情形的?x)话和实?EJB lg的异常处理技术。然而,应用E序异常的某些组合可能会(x)更o(h)解,q且有多U解释。清?9 昄了一个示例?OrderEJB
?ejbCreate()
Ҏ(gu)试图获取 CustomerEJB
的一个远E引用,q会(x)D FinderException
?OrderEJB
?CustomerEJB
都是实体 EJB lg。?zhn)应该如何解?ejbCreate()
中的q个 FinderException
呢?是把它当作应用程序异常对待呢Q因?EJB 规范把它定义为标准应用程序异常)(j)Q还是当作系l异常对待?
清单 9. ejbCreate() Ҏ(gu)中的 FinderException
public Object ejbCreate(OrderValue val) throws CreateException {
try {
if (value.getItemName() == null) {
throw new CreateException("Cannot create Order without a name");
}
String custId = val.getCustomerId();
Customer cust = customerHome.fingByPrimaryKey(custId);
this.customer = cust;
} catch (FinderException ne) {
//How do you handle this Exception ?
} catch (RemoteException re) {
//This is clearly a System Exception
throw ExceptionLogUtil.createLoggableEJBException(re);
}
return null;
}
|
虽然没有什么东襉K止?zhn)?FinderException
当应用程序异常对待,但把它当pȝ异常对待?x)更好。原因是QEJB 客户机們于把 EJB lg当黑对待。如?createOrder()
Ҏ(gu)的调用者获得了一?FinderException
Q这对调用者ƈ没有M意义?OrderEJB
正试图设|客戯E引用这件事对调用者来说是透明的。从客户机的角度看,p|仅仅意味着该订单无法创建?
q类情Ş的另一个示例是Q会(x)?EJB lg试图创徏另一个会(x)?EJBQ因而导致了一?CreateException
。一U类似的情Ş是,实体 EJB Ҏ(gu)试图创徏一个会(x)?EJB lgQ因而导致了一?CreateException
。这两个异常都应该当作系l异常对待?
另一个可能碰到的挑战是会(x)?EJB lg在它的某个容器回调方法中获得了一?FinderException
。?zhn)必须逐例处理q类情况。?zhn)可能要决定是?FinderException
当应用程序异常还是系l异常对待。请考虑清单 1 的情况,其中调用者调用了?x)?EJB lg?deleteOldOrder
Ҏ(gu)。如果我们不是捕?FinderException
Q而是它抛出Q会(x)怎么样呢Q在q一特定情况中,?FinderException
当系l异常对待似乎是W合逻辑的。这里的理由是,?x)?EJB lg們于在它们的方法中做许多工作,因ؓ(f)它们处理工作情形,q且它们对调用者而言是黑?
另一斚wQ请考虑?x)?EJB 正在处理下订单的情Ş。要下一个订单,用户必须有一个简??但这个特定用户却q没有。业务逻辑可能希望?x)?EJB 昑ּ地通知用户她的档丢׃。丢q档很可能表现Z(x)?EJB lg中的 javax.ejb.ObjectNotFoundException
Q?FinderException
的一个子c)(j)。在q种情况下,最好的办法是在?x)?EJB lg中捕?ObjectNotFoundException
q抛Z个应用程序异常,让用L(fng)道她的简档丢׃?
即是有了很好的异常处理{略Q另一个问题还是经怼(x)在测试中出现Q而且在品中也更加重要。编译器和运行时优化?x)改变一个类的整体结构,q会(x)限制(zhn)用堆栈跟t实用程序来跟踪异常的能力。这是(zhn)需要代码重构的帮助的地斏V?zhn)应该把大的方法调用分割?f)更小的、更易于理的块。而且Q只要有可能Q异常类型需要多就划分为多;每次(zhn)捕获一个异常,都应该捕获已规定好类型的异常Q而不是捕h有类型的异常?/P>
l束?/FONT>
我们已经在本文讨Z很多东西Q?zhn)可能想知道我们已l讨论的主要设计是否都物有所倹{我的经验是Q即便是在中型目中,在开发周期中Q?zhn)的付出就已经能看到回报,更不用说试和品周期了。此外,在宕机对业务h毁灭性媄(jing)响的生pȝ中,良好的异常处理体pȝ构的重要性再怎么也不q分?/P>
我希望本文所展示的最?jng)_法对(zhn)有益。要深入理解q里提供的某些信息,请参?参考资?/FONT>部分中的清单?
参考资?
关于作?/FONT>
 |
![Srikanth Shenoy 的照? src=]()
|
Srikanth Shenoy 专门从事大型 J2EE ?EAI 目的体pȝ构、设计、开发和部v工作。他?Java q_一出现时就q上了它Q从此便全心投入。Srikanth 已经帮他的制造业、物业和金融业客户实现?Java q_“一ơ编写,随处q行”的梦想。?zhn)可以通过 srikanth@srikanth.org与他联系? |