当前位置: 首页 > 学习 > 电脑学习 > 程序设计 > JAVA > EJB > 正文

EJB 异常处理的最佳做法(1)

http://www.zk168.com.cn  招考学习网 2006-4-11 3:05:58
-----------------------------------------------------------[交流]-[打印]-[发送]-[收藏]--
EJB 异常处理的最佳做法(1)

随着 J2EE 成为企业开发平台之选,越来越多基于 J2EE 的应用程序将投入生产。J2EE 平台的重要组件之一是 Enterprise JavaBean(EJB)API。J2EE 和 EJB 技术一起提供了许多优点,但随之而来的还有一些新的挑战。特别是企业系统,其中的任何问题都必须快速得到解决。在本文中,企业 Java 编程老手 Srikanth Shenoy 展现了他在 EJB 异常处理方面的最佳做法,这些做法可以更快解决问题。
在 hello-world 情形中,异常处理非常简单。每当碰到某个方法的异常时,就捕获该异常并打印堆栈跟踪或者声明这个方法抛出异常。不幸的是,这种办法不足以处理现实中出现的各种类型的异常。在生产系统中,当有异常抛出时,很可能是最终用户无法处理他或她的请求。当发生这样的异常时,最终用户通常希望能这样:

有一条清楚的消息表明已经发生了一个错误

有一个唯一的错误号,他可以据此访问可方便获得的客户支持系统

问题快速得到解决,并且可以确信他的请求已经得到处理,或者将在设定的时间段内得到处理
理想情况下,企业级系统将不仅为客户提供这些基本的服务,还将准备好一些必要的后端机制。举例来说,客户服务小组应该收到即时的错误通知,以便在客户打电话求助之前服务代表就能意识到问题。此外,服务代表应该能够交叉引用用户的唯一错误号和产品日志,从而快速识别问题 ? 最好是能把问题定位到确切的行号或确切的方法。为了给最终用户和支持小组提供他们需要的工具和服务,在构建一个系统时,您就必须对系统被部署后可能出问题的所有地方心中有数。

在本文中,我们将谈谈基于 EJB 的系统中的异常处理。我们将从回顾异常处理的基础知识开始,包括日志实用程序的使用,然后,很快就转入对 EJB 技术如何定义和管理不同类型的异常进行更详细的讨论。此后,我们将通过一些代码示例来研究一些常见的异常处理解决方案的优缺点,我还将展示我自己在充分利用 EJB 异常处理方面的最佳做法。

请注意,本文假设您熟悉 J2EE 和 EJB 技术。您应理解实体 bean 和会话 bean 的差异。如果您对 bean 管理的持久性(bean-managed persistence(BMP))和容器管理的持久性(container-managed persistence(CMP))在实体 bean 上下文中是什么意思稍有了解,也是有帮助的。请参阅参考资料部分了解关于 J2EE 和 EJB 技术的更多信息。

    异常处理基础知识

解决系统错误的第一步是建立一个与生产系统具有相同构造的测试系统,然后跟踪导致抛出异常的所有代码,以及代码中的所有不同分支。在分布式应用程序中,很可能是调试器不工作了,所以,您可能将用 System.out.println() 方法跟踪异常。System.out.println 尽管很方便,但开销巨大。在磁盘 I/O 期间,System.out.println 对 I/O 处理进行同步,这极大降低了吞吐量。在缺省情况下,堆栈跟踪被记录到控制台。但是,在生产系统中,浏览控制台以查看异常跟踪是行不通的。而且,不能保证堆栈跟踪会显示在生产系统中,因为,在 NT 上,系统管理员可以把 System.out 和 System.err 映射到 ´ ´,在 UNIX 上,可以映射到 dev/null。此外,如果您把 J2EE 应用程序服务器作为 NT 服务运行,甚至不会有控制台。即使您把控制台日志重定向到一个输出文件,当产品 J2EE 应用程序服务器重新启动时,这个文件很可能也将被重写。

    异常处理的原则

以下是一些普遍接受的异常处理原则:
如果无法处理某个异常,那就不要捕获它。
如果捕获了一个异常,请不要胡乱处理它。
尽量在靠近异常被抛出的地方捕获异常。
在捕获异常的地方将它记录到日志中,除非您打算将它重新抛出。
按照您的异常处理必须多精细来构造您的方法。
需要用几种类型的异常就用几种,尤其是对于应用程序异常。
第 1 点显然与第 3 点相抵触。实际的解决方案是以下两者的折衷:您在距异常被抛出多近的地方将它捕获;在完全丢失原始异常的意图或内容之前,您可以让异常落在多远的地方。

注:尽管这些原则的应用遍及所有 EJB 异常处理机制,但它们并不是特别针对 EJB 异常处理的。

由于以上这些原因,把代码组装成产品并同时包含 System.out.println 并不是一种选择。在测试期间使用 System.out.println,然后在形成产品之前除去 System.out.println 也不是上策,因为这样做意味着您的产品代码与测试代码运行得不尽相同。您需要的是一种声明控制日志机制,以使您的测试代码和产品代码相同,并且当记录日志以声明方式关闭时,给产品带来的性能开销最小。

这里的解决方案显然是使用一个日志实用程序。采用恰当的编码约定,日志实用程序将负责精确地记录下任何类型的消息,不论是系统错误还是一些警告。所以,我们将在进一步讲述之前谈谈日志实用程序。

    日志领域:鸟瞰

每个大型应用程序在开发、测试及产品周期中都使用日志实用程序。在今天的日志领域中,有几个角逐者,其中有两个广为人知。一个是 Log4J,它是来自 Apache 的 Jakarta 的一个开放源代码的项目。另一个是 J2SE 1.4 捆绑提供的,它是最近刚加入到这个行列的。我们将使用 Log4J 说明本文所讨论的最佳做法;但是,这些最佳做法并不特别依赖于 Log4J。

Log4J 有三个主要组件:layout、appender 和 category。Layou 代表消息被记录到日志中的格式。appender 是消息将被记录到的物理位置的别名。而 category 则是有名称的实体:您可以把它当作是日志的句柄。layout 和 appender 在 XML 配置文件中声明。每个 category 带有它自己的 layout 和 appender 定义。当您获取了一个 category 并把消息记录到它那里时,消息在与该 category 相关联的各个 appender 处结束,并且所有这些消息都将以 XML 配置文件中指定的 layout 格式表示。

Log4J 给消息指定四种优先级:它们是 ERROR、WARN、INFO 和 DEBUG。为便于本文的讨论,所有异常都以具有 ERROR 优先级记录。当记录本文中的一个异常时,我们将能够找到获取 category(使用 Category.getInstance(String name) 方法)的代码,然后调用方法 category.error()(它与具有 ERROR 优先级的消息相对应)。

尽管日志实用程序能帮助我们把消息记录到适当的持久位置,但它们并不能根除问题。它们不能从产品日志中精确找出某个客户的问题报告;这一便利技术留给您把它构建到您正在开发的系统中。

要了解关于 Log4J 日志实用程序或 J2SE 所带的日志实用程序的更多信息,请参阅参考资料部分。

    异常的类别

异常的分类有不同方式。这里,我们将讨论从 EJB 的角度如何对异常进行分类。EJB 规范将异常大致分成三类:

JVM 异常:这种类型的异常由 JVM 抛出。OutOfMemoryError 就是 JVM 异常的一个常见示例。对 JVM 异常您无能为力。它们表明一种致命的情况。唯一得体的退出办法是停止应用程序服务器(可能要增加硬件资源),然后重新启动系统。


应用程序异常:应用程序异常是一种定制异常,由应用程序或第三方的库抛出。这些本质上是受查异常(checked exception);它们预示了业务逻辑中的某个条件尚未满足。在这样的情况下,EJB 方法的调用者可以得体地处理这种局面并采用另一条备用途径。


系统异常:在大多数情况下,系统异常由 JVM 作为 RuntimeException 的子类抛出。例如,NullPointerException 或 ArrayOutOfBoundsException 将因代码中的错误而被抛出。另一种类型的系统异常在系统碰到配置不当的资源(例如,拼写错误的 JNDI 查找(JNDI lookup))时发生。在这种情况下,系统就将抛出一个受查异常。捕获这些受查系统异常并将它们作为非受查异常(unchecked exception)抛出颇有意义。最重要的规则是,如果您对某个异常无能为力,那么它就是一个系统异常并且应当作为非受查异常抛出。
注:受查异常是一个作为 java.lang.Exception 的子类的 Java 类。通过从 java.lang.Exception 派生子类,就强制您在编译时捕获这个异常。相反地,非受查异常则是一个作为 java.lang.RuntimeException 的子类的 Java 类。从 java.lang.RuntimeException 派生子类确保了编译器不会强制您捕获这个异常。

    EJB 容器怎样处理异常

EJB 容器拦截 EJB 组件上的每一个方法调用。结果,方法调用中发生的每一个异常也被 EJB 容器拦截到。EJB 规范只处理两种类型的异常:应用程序异常和系统异常。

EJB 规范把应用程序异常定义为在远程接口中的方法说明上声明的任何异常(而不是 RemoteException)。应用程序异常是业务工作流中的一种特殊情形。当这种类型的异常被抛出时,客户机会得到一个恢复选项,这个选项通常是要求以一种不同的方式处理请求。不过,这并不意味着任何在远程接口方法的 throws 子句中声明的非受查异常都会被当作应用程序异常对待。EJB 规范明确指出,应用程序异常不应继承 RuntimeException 或它的子类。

当发生应用程序异常时,除非被显式要求(通过调用关联的 EJBContext 对象的 setRollbackOnly() 方法)回滚事务,否则 EJB 容器就不会这样做。事实上,应用程序异常被保证以它原本的状态传送给客户机:EJB 容器绝不会以任何方式包装或修改异常。

系统异常被定义为受查异常或非受查异常,EJB 方法不能从这种异常恢复。当 EJB 容器拦截到非受查异常时,它会回滚事务并执行任何必要的清理工作。接着,它把该非受查异常包装到 RemoteException 中,然后抛给客户机。这样,EJB 容器就把所有非受查异常作为 RemoteException(或者作为其子类,例如 TransactionRolledbackException)提供给客户机。

对于受查异常的情况,容器并不会自动执行上面所描述的内务处理。要使用 EJB 容器的内部内务处理,您将必须把受查异常作为非受查异常抛出。每当发生受查系统异常(如 NamingException)时,您都应该通过包装原始的异常抛出 javax.ejb.EJBException 或其子类。因为 EJBException 本身是非受查异常,所以不需要在方法的 throws 子句中声明它。EJB 容器捕获 EJBException 或其子类,把它包装到 RemoteException 中,然后把 RemoteException 抛给客户机。

虽然系统异常由应用程序服务器记录(这是 EJB 规范规定的),但记录格式将因应用程序服务器的不同而异。为了访问所需的统计信息,企业常常需要对所生成的日志运行 shell/Perl 脚本。为了确保记录格式的统一,在您的代码中记录异常会更好些。

注:EJB 1.0 规范要求把受查系统异常作为 RemoteException 抛出。从 EJB 1.1 规范起规定 EJB 实现类绝不应抛出 RemoteException。

    常见的异常处理策略

如果没有异常处理策略,项目小组的不同开发者很可能会编写以不同方式处理异常的代码。由于同一个异常在系统的不同地方可能以不同的方式被描述和处理,所以,这至少会使产品支持小组感到迷惑。缺乏策略还会导致在整个系统的多个地方都有记录。日志应该集中起来或者分成几个可管理的单元。理想的情况是,应在尽可能少的地方记录异常日志,同时不损失内容。在这一部分及其后的几个部分,我将展示可以在整个企业系统中以统一的方式实现的编码策略。您可以从参考资料部分下载本文开发的实用程序类。

清单 1 显示了来自会话 EJB 组件的一个方法。这个方法删除某个客户在特定日期前所下的全部订单。首先,它获取 OrderEJB 的 Home 接口。接着,它取回某个特定客户的所有订单。当它碰到在某个特定日期之前所下的订单时,就删除所订购的商品,然后删除订单本身。请注意,抛出了三个异常,显示了三种常见的异常处理做法。(为简单起见,假设编译器优化未被使用。)

清单 1. 三种常见的异常处理做法
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 }


现在,让我们用上面所示的代码来研究一下所展示的三种异常处理做法的缺点。

抛出/重抛出带有出错消息的异常
NamingException 可能发生在行 101 或行 108。当发生 NamingException 时,这个方法的调用者就得到 RemoteException 并向后跟踪该异常到行 119。调用者并不能告知 NamingException 实际是发生在行 101 还是行 108。由于异常内容要直到被记录了才能得到保护,所以,这个问题的根源很难查出。在这种情形下,我们就说异常的内容被“吞掉”了。正如这个示例所示,抛出或重抛出一个带有消息的异常并不是一种好的异常处理解决办法。

记录到控制台并抛出一个异常
FinderException 可能发生在行 102 或 109。不过,由于异常被记录到控制台,所以仅当控制台可用时调用者才能向后跟踪到行 102 或 109。这显然不可行,所以异常只能被向后跟踪到行 122。这里的推理同上。

包装原始的异常以保护其内容
RemoteException 可能发生在行 102、106、109、113 或 115。它在行 123 的 catch 块被捕获。接着,这个异常被包装到 EJBException 中,所以,不论调用者在哪里记录它,它都能保持完整。这种办法比前面两种办法更好,同时演示了没有日志策略的情况。如果 deleteOldOrders() 方法的调用者记录该异常,那么将导致重复记录。而且,尽管有了日志记录,但当客户报告某个问题时,产品日志或控制台并不能被交叉引用。

EJB 异常处理探试法
EJB 组件应抛出哪些异常?您应将它们记录到系统中的什么地方?这两个问题盘根错结、相互联系,应该一起解决。解决办法取决于以下因素:

您的 EJB 系统设计:在良好的 EJB 设计中,客户机绝不调用实体 EJB 组件上的方法。多数实体 EJB 方法调用发生在会话 EJB 组件中。如果您的设计遵循这些准则,则您应该用会话 EJB 组件来记录异常。如果客户机直接调用了实体 EJB 方法,则您还应该把消息记录到实体 EJB 组件中。然而,存在一个难题:相同的实体 EJB 方法可能也会被会话 EJB 组件调用。在这种情形下,如何避免重复记录呢?类似地,当一个会话 EJB 组件调用其它实体 EJB 方法时,您如何避免重复记录呢?很快我们就将探讨一种处理这两种情况的通用解决方案。(请注意,EJB 1.1 并未从体系结构上阻止客户机调用实体 EJB 组件上的方法。在 EJB 2.0 中,您可以通过为实体 EJB 组件定义本地接口规定这种限制。)


计划的代码重用范围:这里的问题是您是打算把日志代码添加到多个地方,还是打算重新设计、重新构造代码来减少日志代码。


您要为之服务的客户机的类型:考虑您是将为 J2EE Web 层、单机 Java 应用程序、PDA 还是将为其它客户机服务是很重要的。Web 层设计有各种形状和大小。如果您在使用命令(Command)模式,在这个模式中,Web 层通过每次传入一个不同的命令调用 EJB 层中的相同方法,那么,把异常记录到命令在其中执行的 EJB 组件中是很有用的。在多数其它的 Web 层设计中,把异常记录到 Web 层本身要更容易,也更好,因为您需要把异常日志代码添加到更少的地方。如果您的 Web 层和 EJB 层在同一地方并且不需要支持任何其它类型的客户机,那么就应该考虑后一种选择。


您将处理的异常的类型(应用程序或系统):处理应用程序异常与处理系统异常有很大不同。系统异常的发生不受 EJB 开发者意图的控制。因为系统异常的含义不清楚,所以内容应指明异常的上下文。您已经看到了,通过对原始异常进行包装使这个问题得到了最好的处理。另一方面,应用程序异常是由 EJB 开发者显式抛出的,通常包装有一条消息。因为应用程序异常的含义清楚,所以没有理由要保护它的上下文。这种类型的异常不必记录到 EJB 层或客户机层;它应该以一种有意义的方式提供给最终用户,带上指向所提供的解决方案的另一条备用途径。系统异常消息没必要对最终用户很有意义。
处理应用程序异常
在这一部分及其后的几个部分中,我们将更仔细地研究用 EJB 异常处理应用程序异常和系统异常,以及 Web 层设计。作为这个讨论的一部分,我们将探讨处理从会话和实体 EJB 组件抛出的异常的不同方式。

(未完待续)
-----------------------------------------------------------[交流]-[打印]-[发送]-[收藏]--
最新入库:
 
·实质、过程及意义——阿多尔诺“否定的辩证法”探微
·从Ontology的译名之争看哲学术语的翻译原则
·论马克思主义哲学经典的解释——解释学方法及其在马克
·中国哲学当前的核心与周边问题
·和合学与21世纪文化价值和科技
·中国文化的和合精神与21世纪
·宗教之间理当相互宽容
·上半个世纪的自由主义
·殷周至春秋时期神人关系之演进
·大学之道:构建以“三纲八目”为核心的道德修养体系
相关内容:
 
·BOG压缩机在液化石油气基地的应用
·LFCB-102型微波分相差动保护的应用
·基于PB6和ORACLE8开发“劳动信息管理系统”
·利用TDC组件实现对WEB页面的交互操作
·关于SBS改性沥青配合比设计及施工技术的探讨
·PowerBuilder的分布式计算技术及其应用
·PowerBuilder和MapInfo的集成开发技术
·基于Visual Basic快速开发现场电视监控系统
·PBX濒临被取代的危机—论程控交换机的生存与发展
·ASP技术访问WEB数据库 2
网友点评:
 
会员名称:
密码:匿名 ·注册·忘记密码?
评论内容:
(最多300个字符)
  查看评论
友情提醒:
 1.库中的资料大都来自互联网、网友上传、各类书籍,在录入的过程中难免会出现错误,恳请网
 友来信指正!
 2.如果网友在本库中未能找到所需要的材料,请登陆到我们的论坛《招考学习网》版块!
 3.考友想加入招考学习网的编辑部,请发信到XueXiWang#Gmail.com(#改为@)附带个人简历
 4.如需转载请注明出处及作者,谢谢合作!
 5.如果您有更好的建议或意见请EMAIL:XueXiWang#Gmail.com (#改为@)
 6.凡标题中有注有“[NO]”字样均不含答案且答案整理中.
 7.如本库中转载文章涉及版权等问题,请相关网站或作者在两周内发邮件通知(EMAIL:  XueXiWang#Gmail.com (#改为@))我们,我们接到通知后立即删除该文章及链接!
你问我答 更多>>