这是原文翻译过来的
原文出处: Effective EJB:
Make EJBs Work For You
Java开发正处于一个十字路口。开放的标准已经为Java平台和语言带来了很多益处,但它们也带来了一些问题。开发人员经常对Java开发的复杂性感到头疼。更糟糕的是,复杂度是如此严重,以至于实际的业务问题反而处于次要地位。
J2EE规范提供了许多API、标准和开放的终端,允许架构师、设计师和开发人员建立出众的企业系统。在权衡选择适当技术时必须小心。
技术的发展为开发人员带来的更多是困惑(除非开发人员是无所不能的),而不是帮助他们解决这些问题。通常情况下,架构师和开发人员将大多数时间花在对选中框架的支持上,而不是用来解决手头的业务问题。
本文将讨论在使用Enterprise Java Beans (EJB)规范设计和开发出众的J2EE应用程序的过程中所涉及到的技术。尽管本文的目的不是展示如何使用EJB,但是我还是会谈到EJB开发过程中的各种陷阱,主要集中于现实世界中的一些反模式,这些反模式已经偷偷进入您的开发过程中。
按部就班地学习固然不错,但是由于日益缩短的开发周期,从其他人的错误中学习是更明智的方法。对常出现危险的位置的全面了解将有助于采用主动的策略,而这正是本文的主题。我们将从J2EE应用程序的EJB上下文开始,并讨论存在于EJB开发中的一些潜在危险。
有效的EJB决策
软件是一种设计艺术,并且设计是不断延伸的。随着每种激动人心的技术(如:EJB规范)的出现,疲倦的工程师往往都急切地采用新技术。这是大多数EJB项目会失败的原因之一。通常它们一败涂地,以至于得不到任何回报。这样更明智一些:慎重对待EJB的抉择,而不是仅仅因为它是一种很热门、很有吸引力、有前途的技术就采用它。
论及用于构建分布式的事务型长期业务解决方案的组件架构,要决定是否要在软件项目中使用EJB,需要用良好的设计实践进行认真的分析和主动的规划。
不明智的选择(用金锤去砸苍蝇)
“当您手中有一把锤子时,任何东西看上去都像钉子。”这个谚语适用于大多数EJB选择。对于大多数项目来说,EJB可能不是最佳选择,因为存在如下情况:
- 您已经购买了服务器,并且已经拥有了一个可以使用的EJB容器;
- 您正在进行企业Java开发(您的企业是否真是一个企业?);
- 您希望有一个完全可以移植的架构(EJB真的可以移植吗?);
- 您拥有一个容器,而EJB允许您将大多数工作委托给该容器(这将减少开发成本/时间,确保快速上市)。
解决方案(明智的选择)
图1 展示了在项目规模和成本方面,简单的POJO(Plain Old Java Objects)解决方案与EJB解决方案之间的折衷。
选择EJB解决方案
- 努力迅速达到收支平衡点;
- 如果您的项目比较复杂,那么就选择EJB(如图中所示,EJB项目开始比较复杂,并且成本较高,但随着项目规模的增长,它的成本增长比较缓慢);
- 如果EJB容器提供的服务是企业所需的,那么就选择EJB。换句话说,如果下列问题的答案是肯定的,那么就选择EJB:
- 组件是否是分布式的?
- 是否需要处理事务?
- 业务组件层是否有安全性需求?
- 是否需要在托管环境中运行持久性框架?
- 应用是否需要较高的可伸缩性?
并非一切都是EJB
在初学者和中等水平的用户中间最常见的对EJB的误解是,如果使用EJB的话,每个组件都是一个EJB。
错了,这种判断很少是成立的。某个组件或子系统可能真的是候选的EJB,但是其他组件或子系统可能只是POJO。这种混合模式的设计将很难进行,但是慎重对待每个组件/子系统级的设计决策可以让转出时的工作变得比较容易。
粗粒度和细粒度的服务
在子系统级选择EJB的一个重要问题是理解EJB服务。EJB组件呈现两种风格:一组作坊组件,称为会话bean;以及一组持久性组件,称为实体bean。会话bean通常是粗粒度服务,利用容器的功能提供分发、事务管理和安全性。对于非EJB组件,实现这些服务是开发人员的责任。那些大量使用了这些服务的组件通常是会话EJB的正确候选者。
EJB规范通过实体bean实施对细粒度持久性服务的支持。每个实体bean本身也都可以是分布式的、支持事务的、安全的,因此存在细粒度和粗粒度服务的复杂混合。这种混合往往使实体bean组件很难管理。如果只需要一个组件是持久的,那么很少需要将该组件作为EJB。存在其他更易于维护的、流行的轻量级持久性框架(例如:JDO、Hibernate等)。
有效的EJB接口
EJB设计的一个关键问题是创建接口。接口是EJB组件的通用语。它们是将EJB组件所提供的服务暴露给外部世界的手段。拙劣的接口设计将会导致EJB很难维护和修改。
接口设计的考虑因素
您的网络管道有多大? 到目前为止,EJB采用的还是RMI。它涉及到远程过程调用。位置的透明性只会提高网络的复杂性。EJB调用涉及到通过网络编组和解组参数(非常重要!)。在设计这些远程接口时必须十分小心。如果有一个可以允许大量数据迅速流过的大管道,那么接口可以是粗粒度的。反之,如果拥有的带宽较窄,那么细粒度的接口结合轻量级的参数编组会更好一些。
是否需要外观(fa?ade)?事先应该考虑好是否要使用外观作为其他EJB组件的网关,事后再思考就没有什么意义了。一旦决定使用外观,就可以设计外观来返回作为值的集合的对象。细粒度访问将由外观来处理,并且由于它通常在容器内,所以将会十分有效。这样就可以减少到远程服务器的往返次数。
使用外观。但是如何使用外观才好?在设计EJB接口时,对于设计远程接口而言,关键是要尽量避免状态,而且远程接口使用粗粒度即可。粗粒度接口设计不一定意味着在一个外观中集中EJB层中的所有方法。通常情况下,将多个外观用作EJB组件的网关是比较好的方法。在设计基于EJB的架构时,最安全的方法是在面向对象的域模型与过程远程服务层之间找到一个平衡点。
有效的异常处理
异常处理是EJB开发中另一个最让人困惑的领域。从本质上来说,异常是对预期行为的偏离。这些中断在分布式架构中非常常见,因为涉及到太多异构环境,而这些环境全都通过网络连接。一个网络故障就能引发一场灾难。EJB中的异常处理特别复杂,而且涉及两种类型的异常。下面是处理异常的一些指导原则。
- 把逻辑从异常处理块中拿出。在异常处理代码中插入应用逻辑是一个坏习惯,它经常会产生难于理解、修改和调试的代码。在EJB编程中,故意把逻辑放入异常处理代码的用例是事务回滚。在出现异常时回滚事务将打乱程序流。此外,回滚一个容器托管的事务不是一项简单的任务。相反,EJB规范推荐使用EJBContext.setRollBackOnly()。其次,在异常中回滚事务不总是很合适。再次,事务回滚在事务实现之外。如果应用程序运行在一个具有不受支持的事务属性的EJB中,那么抛出异常将会引起混乱:是否要回滚挂起的封闭事务?
- 不要屏蔽异常。异常记录是任何组件设计的重要组成部分。开发人员最常犯的一个错误就是,打印出异常之后就像什么事情都没有发生一样继续进行。出现异常通常表示一些事情出现了问题。问题可能出在容器的某一部分上,或者就在应用程序代码当中。只记录异常而没有详细信息会使调试非常困难。Java提供了一个记录完整的异常跟踪的绝好方法。在大多数情况下,异常跟踪将提供调试一个问题所需要的所有信息。在异常跟踪上附加自定义的详细信息将有助于了解当前的情况,因此也是比较好的做法。
- 抛出适当的异常。这条原则总是正确的。许多EJB实现中都有一个坏习惯,将应用异常包装到容器异常中,并在客户端抛出。这种习惯源于对容器的误解:在开发EJB时,容器会为您处理一切。然而,容器异常是有范围限制的,故障只发生在容器的某一部分上,而应用异常则是运行时应用故障。应用异常的一个例子是无效的信用卡号,它不应该被包装到容器异常(如EJBException)中。应用异常应该在其他常规Java类中进行适当处理。
- 查找“热土豆”问题。“热土豆”是一种特殊情况,在这种情况中,MDB反复收到同样的消息,并在处理消息时抛出异常。当JMS服务器没有接收到传送给消费者的消息的确认时,服务器唯一的办法就是重新发送消息。如果处理消息的用户没有正确地处理异常情况,就会无限制地重复发送同一消息。
在开发MDB时,应该特别注意识别可能引起“热土豆”问题的代码区域。其解决方案是将确认和消息处理分为两条执行路径。一接收到消息就立即确认。当在消息处理过程中出现异常时,可能需要将异常写入错误队列。
有效的EJB持久性
EJB规范通过一个称为实体bean的特殊bean类来处理持久性服务。实体bean的目标是将域数据模型抽象化。就这一点而言,如果底层的数据源是关系型数据库管理系统,那么实体bean代表的就是数据库表中的行。最初的规范激起了许多关于实体bean及其用法的争论。此后,持久性API在1.1和2.0规范中重写了两次。
由于持久性是一种细粒度服务,它必然不适合EJB服务的粗粒度特性。这带来了一些困惑:如何使用实体bean才最好?在做出使用实体bean的决定之前,建议先对其他持久性模型进行认真的评估。如果决定使用实体bean,那么应该仔细考虑和评估下面的问题。
通过远程接口将实体Bean直接暴露给客户端
由于实体bean的细粒度特性,当直接把它们暴露给客户端时,很容易产生反模式。一个最常遇到的问题可能是n + 1问题。它是指为了检索一个业务实体的n个属性,需要n+1次远程调用。额外的一次调用是从EJB容器获得远程存根。通过无格式的POJO DAO实现同样的检索功能会更简单一些。只需用一个JDBC调用来检索行。
一个与暴露实体bean相关的更微妙的问题是事务完整性的缺失。考虑一个具有三个mutator方法的实体bean。进一步假定客户端需要更新事务中两个mutator方法所代表的实体的两列。客户端如何保证实体bean上两个连续的更新方法之间的事务完整性?唯一的方法是使用会话外观来包装实体bean。
避免应用程序连接
在应用程序代码中管理实体关系将会引起严重的性能问题。Java没有针对数据库查找进行过优化。在Java代码内部模拟应用程序连接是一个坏的编程习惯。数据库使用成熟的技术来优化查询访问计划,从而将数据比较的次数降至最少。例如,在应用程序中,给定一个地址实体查找Person实体的动作无法调节。这些类型的关系能够按照CMR字段(源于EJB
2.0)得到最好的表示。应用程序在循环结构中为模拟关系连接而进行的比较的次数将会对应用程序的性能产生重要影响。
不要使用长的主键
用长的主键设计实体bean将会导致性能随着数据存储区中数据的增长而下降。主键被数据库用于查找。数据库索引大量利用主键来建立哈希表。与短主键相比,长主键在建立哈希表的过程中需要更多的计算。数据库缓存索引字段来优化关系连接的性能。长主键往往会在缓存中占用许多空间,从而促生系统失效情况。
然而,多长才算长呢?对于主键可以有多长,不存在基准。长度依赖于目标数据库表中的数据量。
进行有效EJB性能调优的指导原则
不管在开发过程中是否采用了良好的设计、编码和工程方面的实践,系统都需要进行调优,对于基于EJB的系统来说这尤其正确,因为它们非常复杂,难以进行高级测试。下面是一些性能调优的指导原则。
- 什么是性能?性能是对应用程序在企业的环境约束下达到期望的能力的衡量。
- 性能调优的软件工程方法——了解限制。期望只有32MB RAM和DEC PDP 11的机器吞吐量提高10倍是不可能的。
- 测量,不要猜测!用常数定义期望值。只重复需要提高的性能是没有用的。要给出确定的数字,例如,方法mytrans()应该在三秒内完成。大多数情况下,最好让终端用户提出一些预期的性能数字。
- 使用容器。EJB容器被设计用来通过集群、负载均衡和其他方法来提供高级别的可伸缩性。在着手调优性能前了解这些功能是非常重要的。
- 良好的编码是第一位的。在任何合适的时间和位置使用设计模式来支持高度的可维护性。这将有助于使应用程序性能调优更轻松。
- 收集统计信息。在每次调优前和调优后,收集统计信息。这些数字对于进一步规划具有无法衡量的价值。比较这些数字,分析获得的性能提升,如果必要的话,再进行进一步的规划。
- 逐步进行——罗马不是一天建成的。不要把所有事情都放在第一步中。逐步进行,在每个步骤完成时进行测量。这将有助于隔离性能瓶颈。
- 知道何时停止。知道何时停止是调优的关键。过度调优将导致可怕的后果。明确定义的性能标准将会帮助鉴别何时停止进一步的调优。
- 除了好的人员,还要选择好的工具。使用工具来引导您完成调优过程。例如,JUnitPerf将帮助您找出各种场景下的响应时间。配置文件是用于隔离问题区域的重要工具。
- 不要总体规划,要不断调整计划。不要编写调优的总体规划,并遵照它来进行。性能调优是一个迭代和可重复的过程。在每一步完成后,度量并重新规划进度,并定义新的起点。总体规划对于软件调优来说并不实用。
其他
把XML用作银弹
以灵活性和可伸缩性的名义用XML来填充JMS消息不能解决一切问题。由于XML是新的热门技术,大多数设计师/开发人员往往过度使用XML。当存在其他的灵活选择时,XML的过度使用会严重影响应用程序的可伸缩性。
XML是一项伟大的技术,它将不同种类的环境集中在一起,但最好能理智地使用它。记住,XML也增加了开销。因为XML是无类型的(所有的内容都是字符串),所以必须进行类型转换和检查。此外,分析庞大的XML消息是一个非常耗费资源的过程。XML的过度使用通常出现在JMS实现中。在这种情况下,MDB会花费许多时间进行类型检查并分析收到的XML消息。
在可移植性方面不要想太多
EJB规范的目标之一是允许创建可移植组件。然而在现实中,它必须依赖于容器提供者所提供的扩展和增强。例如,每个供应商都有其私有的部署描述符,该描述符允许调优EJB实例的行为。重要的是要认识到,可移植性是一个希望有的功能而不是必需的。企业很少改变应用服务器,很可能要在同一个服务器上部署EJB。因此充分利用容器提供者所提供的额外功能是很明智的做法。
谨慎使用实体Bean
正如前面所讨论的,实体bean本质上不适合分布式服务的粗粒度特性。此外,它们非常耗费资源,而且难以更改和维护。使用实体bean的最初目标是能够在机器A上部署一个域模型,在机器B上部署另一个域模型,使它们能够通过位置透明性进行无缝地互操作。由于通过网络连接,这很少是正确的。这导致开发人员调整实体bean模型,而这只能让事情变得更糟。
随着轻量级容器的逐渐流行,我们有了非常好的提供透明持久性的替代方案。例如,来自于开源领域的Hibernate。它向POJO提供了透明持久性服务。它易于配置,不需要完全的J2EE容器。Sun制定了一个通过JDO实现透明持久性的新标准。尽管JDO还处在初期阶段,并且还没有完全被J2EE供应商所接受,但它提供了许多希望,而且它是一个相当简单的API。
测试,测试,再测试
分布式组件是长期投资,它们在公司的IT基础架构中起着很重要的作用。它们被大量使用,一个故障就会引起巨大的损失。必须小心确保中间件组件在生产中运行良好。这可以通过开发生命周期中每一个阶段的严格测试来保证。EJB组件需要专门的测试,因为它们存在于一个托管环境(容器)中。从设计到编码到集成和部署,EJB组件应该使用完全真实的用例进行测试,以便确保更平滑的生产过渡。
EJB 3.0展望
在实现通过简化开发来提高开发人员的生产力的承诺方面,EJB架构可能是仅有的败得如此惨重的J2EE组件。EJB 3.0正在通过降低EJB的复杂性再次试图实现这一承诺。
EJB 3.0减少了开发人员需要提供的编程工件的数量,消除了需要实现的回调方法或将其减至最少,降低了实体bean编程模型和O/R映射模型的复杂性。下面是EJB
3.0规范的主要新特性。
- 基于注释的EJB编程模型。EJB 3.0充分利用了Java 5(又称为Tiger)中引入的基于注释的编程模型。在EJB
3.0中,所有类型的企业bean都只是POJO。注释用于定义引用、回调、远程接口、创建方法等。这使编程更加简单,并且模型非常整洁。此外,它使开发人员从受约束的框架实现中解脱出来。 - 对POJO风格的EJB开发的支持。EJB 3.0支持POJO风格的EJB。这意味着EJB类不需要实现相应的接口。只需为编译器设置一个注释来帮助理解类的行为。这使得转换现有的POJO服务(例如EJB)更加简单。
早期编程模型的问题之一是EJB需要许多类和描述符。从EJB
3.0开始,不再需要接口或部署描述符。只需一个得到充分注释的EJB类(本质上是一个POJO),为容器提供运行时信息。
- 用于实体bean的新增持久性模型:遵照新的会话bean模型,EJB
3.0规范中的实体bean只是POJO。此外,EJBQL也已经进行了重要修改,添加了很多功能。实体bean类可以像一个无格式的Java Bean类一样,所有没有用@Transient注释标记的字段都被假定为持久化的。对象关系映射已经从早期规范中的抽象持久化模式模型转变为支持Hibernate的持久型模型。 - 在容器外测试的能力:EJB架构的一个主要缺点是,无法在托管环境外对其进行测试。EJB 3.0规范的初始草案包含一些关于容器外测试能力的想法。然而,这还只处于草稿阶段,如果它能成功出现在最终版本中,那将会是对规范的重要补充。
结束语
EJB仍然是J2EE领域最重要和最具发展潜力的技术。EJB的主要目标是标准化企业中间件编程。正如所有复杂的技术一样,它很容易被误用和误解。有效使用EJB并发挥其最大潜能的关键是识别EJB的业务上下文、评估设计决策、应用良好的工程原则,以及创建高效的EJB。一旦注意合理使用EJB,它就会是成功的希望。而如果处理不合理,它则会变成您的噩梦。
从决定使用EJB到生产部署,每一步都需要认真地考虑、分析和规定。本文为EJB开发提供了一些指导原则,并展示了EJB开发中的一些潜在的灰色领域。然而,新的反模式不断出现,这要由架构师、工程师和开发人员来解决。
EJB 3.0确实是向简化EJB开发迈出的一大步,这不仅表现在其编码部分。能够编写简单的EJB将使维护更加方便,并降低生产中的风险。J2EE和扩展的EJB现在是企业应用程序的默认开发平台。
要有效使用EJB,需要理解EJB只是J2EE的一个扩展。J2EE应用程序不一定要使用EJB。但是,在需要时,EJB能够为应用程序提供重要的优势。开发有效EJB的关键是,避开那些说得天花乱坠的虚假宣传,根据EJB本身的能力来评估该技术。这将帮助您做出更好的决策,理智地使用EJB,最终构建有效的EJB。
希望本文能对您有所帮助!
作者简介 | |
Shankar Itchapurapu是CitiGroup Technologies的一位顾问,Mr. Itchapurapu拥有计算机应用硕士学位。 |