6. 当使用 EJB 组件时,始终使用会话 Facades。
决不要将实体 bean 直接暴露给任何用户类型。对实体 bean 只可以使用本地 EJB 接口(Local EJB interfaces)。
当使用 EJB 组件时,使用一个会话 Facades 是一个确认无疑的最佳实践。实际上,这个通用的实践被广泛地应用到任何分布式的技术中,包括 CORBA、EJB 和 DCOM。从根本上讲,您的应用程序的分布"交叉区域"越是底层化,对小块的数据由于多次重复的网络中继造成的时间消耗就越少。要达到这个目的的方法就是:创建大粒度的 Facades 对象,这个对象包含逻辑子系统,因而可以通过一个方法调用就可以完成一些有用的业务功能。这种方法不但能够降低网络开销,而且在 EJB 内部通过为整个业务功能创建一个事务环境也可以大大地减少对数据库的访问次数。
EJB 本地接口(从 EJB 2.0 规范开始使用)为共存的 EJB 提供了性能优化方法。本地接口必须被您的应用程序显式地进行访问,这需要代码的改变和防止以后配置 EJB 时需要应用程序的改变。由于会话 Facades 和它包含的整个 EJB 对于彼此来说都应该是本地的,我们建议对会话 Facades 后面的实体 bean 使用本地接口。然而,会话 Facades 本身的实现(典型例子如无状态会话 bean)应该设计为远程接口。
为了性能的优化,可以将一个本地接口添加到会话 Facades。这样做利用了这样一个事实:在大多数情况下(至少在 Web 应用程序中),您的 EJB 客户端和 EJB 会共同存在于同一个 Java 虚拟机(JVM)中。另外一种情况,如果会话 Facades 在本地被调用,可以使用 J2EE 应用服务器配置优化(configuration optimizations),例如 WebSphere 中的"No Local Copies"。然而,您必须注意到这些可供选择的方案会将交互方法从按值传递(pass-by-value)改变为按引用传递(pass-by- reference)。这可能会在您的代码中产生很微妙的错误。当您要利用这些方案时,您应该在一开始就考虑其可行性。
如果在您的会话 Facades 中使用远程接口(而不是本地接口),您也可以将同样的会话 Facades 在 J2EE 1.4 中以兼容的方式作为 Web 服务来配置。这是因为 JSR 109(J2EE 1.4 中的 Web 服务部署部分)要求使用无状态会话 bean 的远程接口作为 EJB Web 服务和 EJB 实现的接口。这样做是值得的,因为这样做可以为您的业务逻辑增加客户端类型的数量。
7. 使用无状态会话 bean,而不是有状态会话 bean。
这样做可以使您的系统经得起错误的终止。使用 HttpSession 存储和用户相关的状态。
以我们的观点看来,有状态会话 bean 的概念已经过时了。如果您仔细对其考虑一下,一个有状态会话 bean 实际上与一个 CORBA 对象在体系结构上是完全相同的,无非就是一个对象实例,绑定到一个服务器,并且依赖于服务器来管理其生命周期。如果服务器关闭了,这种对象也就不存在,那么这个 bean 的客户端的信息也就不存在。
J2EE 应用服务器为有状态会话 bean 提供的故障转移(failover)能够解决一些问题,但是有状态的解决方案没有无状态的解决方案易于扩展。例如,在 WebSphere Application Server 中,对无状态会话 bean 的请求,是通过对部署无状态会话的成员集群进行平衡加载来实现。相反地,J2EE 应用服务器不能对有状态 bean 的请求进行平衡加载。这意味着您的集群中的服务器的加载过程会是不均衡的。此外,使用有状态会话 bean 将会再添加一些状态到您的应用服务器上,这也是不好的做法。这样就增加了系统的复杂性,并且在出现故障的情况下使问题变得复杂化。创建健壮的分布式系统的一个关键原则是尽量使用无状态行为。
因此,我们建议对大多数应用程序使用无状态会话 bean 方法。任何在处理时需要使用的与用户相关的状态应该以参数的形式传送到 EJB 的方法中(并且通过使用一种机制如 HttpSession 来存储它)或者从持久性的后端存储(例如通过使用实体 bean)作为 EJB 事务的一部分来进行检索。在合适的情况下,这个信息可以缓存到内存中,但是要注意在分布式的环境中保存这种缓存所潜在的挑战性。缓存非常适合于只读数据。
总之,您要确保从一开始就要考虑到可伸展性。检查设计中的所有设想,并且考虑到当您的应用程序要在多个服务器上运行时,是否也可以正常运行。这个规则不但适合上述情况的应用程序代码,也适用于如 MBean 和其他管理界面的情况下。
避免使用有状态性不只是对 IBM/WebSphere 的建议,这是一个基本的 J2EE 设计原则。
8. 使用容器管理的事务。
学习一下 J2EE 中的两阶段提交事务,并且使用这种方式,而不是开放您自己的事务管理。容器在事务优化方面几乎总是比较好的。
使用容器管理的事务(CMT)提供了两个关键的优势(如果没有容器支持这几乎是不可能的):可组合的工作单元和健壮的事务行为。
如果您的应用程序代码显式地使用了开始和结束事务(也许使用 javax.jts.UserTransaction 或者甚至是本地资源事务),而将来的要求需要组合模块(也许会是代码重构的一部分),这种情况下往往需要改变事务代码。例如,如果模块 A 开始了一个数据库事务,更新数据库,随后提交事务,并且有模块 B 做出同样的处理,请考虑一下当您在模块 C 中尝试使用上述两个模块,会出现什么情况呢?现在,模块 C 正在执行一个逻辑动作,而这个动作实际上将调用两个独立的事务。如果模块 B 在执行中失败了,而模块 A 的事务仍然能被提交。这是我们所不希望出现的行为。如果,相反地,模块 A 和模块 B 都使用 CMT 的话,模块 C 也可以开始一个 CMT(通常通过配置描述符),并且在模块 A 和模块 B 中的事务将是同一个事务的隐含部分,这样就不再需要复杂的重写代码的工作了。
如果您的应用程序在同一个操作中需要访问多种资源,您就要使用两阶段提交事务。例如,如果从 JMS 队列中删除一个消息,并且随后更新基于这条消息的纪录,这时,要保证这两个操作都会执行或都不会执行就变得尤为重要。如果一条消息已经从队列中被删除,而系统没有更新与此消息相关的数据库中的纪录,那么这种系统是不稳定的。一些严重的客户及商业纠纷源自不一致的状态。
我们时常看到一些客户应用程序试图实现他们自己的解决方案。也许会通过应用程序的代码在数据库更新失败的时候 "撤销"对队列的操作。我们不提倡这样做。这种实现要比您最初的想象要复杂得多,并且还有许多其他的情况(想象一下如果应用程序在执行此操作的过程中突然崩溃的情况)。作为替代的方式,应该使用两阶段提交事务。如果您使用 CMT,并且在一个单一的 CMT 中访问两阶段提交的资源(例如 JMS 和大多数数据库),WebSphere 将会处理所有的复杂工作。它将确保整个事务被执行或者都不被执行,包括系统崩溃、数据库崩溃或其他的情况。其实现在事务日志中保存着事务状态。当应用程序访问多种资源的时候,我们怎么强调使用 CMT 事务的必要性都不为过。
9. 将 JSP 作为表示层的首选。
只有在需要多种表示输出类型,并且输出类型被一个单一的控制器及后端支持时才使用 XML/XSLT。
我们常听到一些争论说,为什么您选择 XML/XSLT 而不是 JSP 作为表示层技术。选择 XML/XSLT 的人的观点是,JSP" 允许您将模型和视图混合在一起",而 XML/XSLT 不会有这种问题。遗憾的是,这种观点并不完全正确,或者至少不像白与黑那样分的清楚。实际上,XSL 和 XPath 是编程语言。XSL 是图灵完成的(Turing-complete),尽管它不符合大多数人定义的编程语言,因为它是基于规则的,并且不具备程序员习惯的控制工具。
现在的问题是既然给予了这种灵活性,开发人员就会利用这种灵活性。尽管每个人都认同 JSP 使开发人员容易在视图中加入"类似模型"的行为,而实际上,在 XSL 中也有可能做出一些同样的事情。尽管从 XSL 中进行访问数据库这样的事情会非常困难,但是我们曾经见到过一些异常复杂的 XSLT 样式表执行复杂的转换,这实际上是模型代码。
然而,应该选择 JSP 作为首选的表示技术的最基本的原因是,JSP 是现在支持最广泛的、也是最被广泛理解的 J2EE 视图技术。而随着自定义标记库、JSTL 和 JSP2.0 的新特性的引入,创建 JSP 变得更加容易,并且不需要任何 Java 代码,以及可以将模型和视图清晰的分离开。在一些开发环境中(如 WebSphere Studio)加入了对 JSP(包括对调试的支持)的强大支持,并且许多开发人员发现使用 JSP 进行开发要比使用 XLS 简单,一些支持 JSP 的图形设计工具及其他特征(尤其在 JSF 这样的框架下)使得开发人员可以以所见即所得的方式进行 JSP 的开发,而对于 XSL 有时不容易做到。
最后一个要谨慎考虑使用 JSP 的原因是速度问题。在 IBM 所作的对比 XSL 和 JSP 相对速度的性能测试显示:在大多数情况下,JSP 在生成同样的 HTML 的时候,要比 XSL 快好几倍,甚至使用编译过的 XSL 也是如此。尽管多数情况下这不是问题,但在性能要求很高的情况下,这就会成为问题。
然而,这也不能说,您永远也不要使用 XSL。在一些情况下,XSL 能够表示一组固定的数据,并且可以基于不同的样式表来以不同的方式显示这些数据的能力是显示视图的最佳解决方案。然而,这只是一种例外的情况,而不是通用的规则。如果您只是生成 HTML 来表达每一个页面,那么在大多数情况下,XSL 是一种不必要的技术,并且,它给您的开发人员所带来的问题远比它所能解决的问题多。
10. 当使用 HttpSession 时,尽量只将当前事务所需要的状态保存其中,其他内容不要保存在 HttpSession 中。
启用会话持久性。
HttpSessions 对于存储应用程序状态信息是非常有用的。其 API 易于使用和理解。遗憾的是,开发人员常常遗忘了 HttpSession 的目的----用来保持暂时的用户状态。它不是任意的数据缓存。我们已经见到过太多的系统为每个用户的会话放入了大量的数据(达到兆字节)。那好了,如果同时有 1000 个登录系统的用户,每个用户拥有 1MB 的会话数据,那么就需要 1G 或者更多的内存用于这些会话。要使这些 HTTP 会话数据较小一些,不然的话,您的应用程序的性能将会下降。一个大约比较合适的数据量应该是每个用户的会话数据在 2K-4K 之间,这不是一个硬性的规则,8K 仍然没有问题,但是显然会比 2K 时的速度要慢。一定要注意,不要使 HttpSession 变成数据堆积的场所。
一个常见的问题是使用 HttpSession 缓存一些很容易再创建的信息,如果有必要的话。由于会话是持久性的,进行不必要的序列化以及写入数据是一种很奢侈的决定。相反地,应该使用内存中的哈希表来缓存数据,并且在会话中保存一个对此数据进行引用的关键字。这样,如果不能成功登录到另外的应用服务器的话,就可以重新创建数据。
当谈及会话持久性时,不要忘记要启用这项功能。如果您没有启用会话持久性,或者服务器因为某种原因停止了(服务器故障或正常的维护),则所有此应用服务的当前用户的会话将会丢失。这是件令人非常不高兴的事情。用户不得不重新登录,并且重新做一些他们曾经已经做过的事情。相反地,如果启用了会话持久性, WebSphere 会自动将用户(以及他们的会话)移到另外一个应用服务器上去。用户甚至不知道会有这种事情的发生。我们曾经见到过一些产品系统因为存在于本地代码中令人难以忍受的 bug(不是 IBM 的代码!)而突然崩溃的情况,这这种情况下,上述功能仍然可以运行良好。
11. 在 WebSphere 中,使用动态缓存,并使用 WebSphere servlet 缓存机制。
通过使用这些功能,系统性能可以得到很大的提高,而开销是很小的。并且不影响编程模型。
通过缓存来提高性能的好处是众所周知的事情。遗憾的是,当前的 J2EE 规范没有包括一种用于 servlet/JSP 缓存的机制。然而,WebSphere 提供了对页面以及片断缓存的支持,这种支持是通过其动态缓存功能来实现的,并且不需要对应用程序作出任何改变。其缓存的策略是声明性的,而且其配置是通过 XML 配置描述符来实现的。因此,您的应用程序不会受到影响,并保持与 J2EE 规范的兼容性和移植性,同时还从 WebSphere 的 servlet 及 JSP 的缓存机制中得到性能的优化。
从 servet 及 JSP 的动态缓存机制得到的性能的提高是显而易见的,这取决于应用程序的特性。Cox 和 Martin [Cox] 指出对一个现有的 RDF(资源描述格式)站点摘要 (RSS)servlet,当使用动态缓存时,其性能可以提高 10%。请注意这个实验只涉及到一个简单的 servlet,这个性能的增长量可能并不能反映一个复杂的应用程序。
为了更多地提高性能,将 WebSphere servlet/JSP 结果缓存与 WebSphere 插件 ESI Fragment 处理器、IBM HTTP Server Fast Response Cache Accelerator (FRCA) 和 Edge Server 缓存功能集成在一起。对于繁重的基于读取的工作负荷,通过使用这些功能可以得到许多额外的好处。
12. 为了提高程序员的工作效率,将 CMP 实体 bean 作为 O/R 映射的首选解决方案.
通过 WebSphere 框架(readahead、缓存、隔离级别等)优化性能。如果可能,有选择的应用一些模式来达到提高性能的目的,例如 Fast-Lane 阅读器 [Marinescu]。
对象/关系(O/R)映射是使用 Java 创建企业级的应用程序的基础。几乎每个 J2EE 应用程序都需要一些类型的 O/R 映射。J2EE 厂商提供一种 O/R 映射机制,这种机制在不同的厂商间是可移植的,高效的,并且能够被一些标准及工具很好地支持。这就是 EJB 规范中的 CMP(容器管理的持久性)部分。
早期的 CMP 实现以表现不佳及不支持许多 SQL 结构而著称。然而,随着 EJB 2.0 及 2.1 规范的出现,以及被一些厂商所采纳,并且随着像 IBM WebSphere Studio Application Developer 的出现,这些问题已经不再是问题了。
CMP EJB 组件现在已经被广泛地应用于许多高性能的应用程序中。WebSphere 包括一些优化功能以提高 EJB 组件的性能,优化功能包括:对生命周期的缓存和 read-ahead 能力。这两者优化功能都是配置时的选项,并且不需要对应用程序进行修改或者影响可移植性。
处于缓存状态的生命周期缓存 CMP 状态数据并提供基于时间的无效性。从处于缓存状态的生命周期得到的性能提高可以达到选项 A 的缓存性能,并且仍然可以为您的应用程序提供可伸展性。Read-ahead 能力和容器管理的关系结合使用。这个特性通过在相同的查询中随意地检索相关的数据作为父数据而减少与数据库的交互。如果相关的数据要通过使用并发的查询来访问的话,这种方法可以得到性能的改进。[Gunther]提供了详细的描述以及通过这些特性得到的性能提高的细节。
此外,为了完全优化您的 EJB 组件,当指定隔离级别时要特别注意。尽可能使用最低的隔离级别,并且仍然保持您的数据的完整性。较低的隔离级别可以提供最佳的性能,并且可以降低出现数据库死锁的危险。
这是目前最有争议的最佳实践。已经有大量的文章赞扬 CMP EJB,同样的贬斥声也不绝于耳。然而,这里最基本的问题是数据库开发是困难的。当您开始使用任何持久性解决方案之前,您需要掌握查询以及数据库锁定如何工作这些基础知识。如果您选择使用 CMP EJB,您要确保您已经通过一些书籍(例如 [Brown] 和 [Barcia])知道如何使用它们。在锁定及争用方面有一些微妙的交互难以理解,但是,在您耗费一定的时间及努力后会将其掌握的。
结束语
在这个简短的摘要中,我们已经向您介绍了 J2EE 中的核心模式和最佳实践,这使得 J2EE 开发成为一种可管理的过程。尽管我们并没有给出所有在实践中使用这些模式的必要细节,但是我们希望能够给您足够的指点和指导,以帮助您决定下一步要做什么。