最重要的 Java EE 最佳实践
2004 年 IBM® WebSphere® 开发人员技术期刊中曾发表过一篇名称类似的文章,本文是其更新版本号。
这个修正版中考虑了一些不断变化的技术趋势。更重要的是推荐了一些作者觉得应当广泛遵循、但尚未广泛遵循的实践。
2 评论:
Keys Botzum, 高级技术人员 , EMC
Kyle Brown, 杰出project师, EMC
Ruth Willenborg, 高级技术人员, EMC
Albert Wong, I/T 架构师, IBM India Software Lab Services and Solutions
2007 年 3 月 27 日
引言
在过去的差点儿整整十年中,人们编写了非常多有关 Java™ Platform, Enterprise Edition (Java EE) 最佳实践的内容。如今有十多本书籍和数以百计(可能很多其它)的文章,提供了关于应该怎样编写 Java EE 应用程序的见解。
其实,这方面的參考资料如此之多,而且这些參考资料之间往往还存在着一些矛盾的建议,以至于在这些混杂的内容中进行学习本身也成为了採用 Java EE 的障碍。
因此。为了给刚进入这个领域的客户提供一些简单的指导,我们汇编了这个最重要的最佳实践列表,当中包含我们觉得最重要和最有效的 Java EE 最佳实践。遗憾的是,我们无法仅在 10 大最佳实践中描写叙述全部须要介绍的内容。因此。为了避免遗漏关键的最佳实践和尊重 Java EE 的发展,我们的列表中包括了“19 大”关键的 Java EE 最佳实践。
最重要的最佳实践
您能够点击例如以下链接,立即下载 WebSphere Application Server 软件 V7 版本号,体验其为您带来的新特性及新功能。
- WebSphere Application Server for Developers V7(免费产品)
- WebSphere Application Server V7 试用版
- WebSphere Application Server Express V7 试用版
- WebSphere Application Server Hypervisor Edition V7 试用版(虚拟映像)
很多其它关于 WebSphere Application Server 的技术资源,请參考:
- WebSphere Application Server 产品专题:为您提供了 WebSphere Application Server 相关的文章、教程、多媒体课堂等最新技术资源。
- WebSphere Application Server V7 专题:为您总结了与 WAS V7 相关最新的内容和资源,当中包含入门介绍及开发技巧、配置与管理、迁移、监控与測试等。
- 始终使用 MVC 框架。
- 不要做反复的工作。
- 在每一层都应用自己主动单元測试和測试管理。
- 依照规范来进行开发,而不是依照顾用server来进行开发。
- 从一開始就计划使用 Java EE 安全性。
- 创建您所知道的。
- 当使用 EJB 组件时,始终使用会话 Facade。
- 使用无状态会话 Bean,而不是有状态会话 Bean。
- 使用容器管理的事务。
- 将 JSP 作为表示层的首选。
- 当使用 HttpSession 时,尽量仅仅将当前事务所须要的状态保存当中。其它内容不要保存在 HttpSession 中。
- 充分利用应用server中不须要改动代码的特性。
- 充分利用现有的环境。
- 充分利用应用server环境所提供的服务质量。
- 充分利用 Java EE,不要欺骗。
- 安排进行版本号更新。
- 在代码中全部关键的地方,使用标准的日志框架记录程序的状态。
- 在完毕对应的任务后。请始终进行清理。
- 在开发和測试过程中遵循严格的程序。
1. 始终使用 MVC 框架。
将业务逻辑(Java Bean 和 EJB 组件)从控制器逻辑(Servlet/Struts 操作)和表示逻辑(JSP、XML/XSLT)中清晰地分离出来。良好的分层能够带来很多优点。
这项实践很重要。以致没有其它最佳实践能够与其相提并论。
对于良好的 Java EE 应用程序设计而言。模型-视图-控制器 (MVC) 是至关重要的。它将程序的任务简单地分为以下几个部分:
- 负责业务逻辑的部分(模型,通常使用 Enterprise JavaBeans™ 或传统 Java 对象来实现)。
- 负责用户接口表示的部分(视图)。
- 负责应用程序导航的部分(控制器,通常使用 Java Servlet 或类 Struts 控制器这样相关的类来实现)。
对于 Java EE。有很多关于这个主题的优秀评论。我们特别推荐感兴趣的读者能够參考 [Fowler] 或者 [Brown](请參见參考资料部分)的评论,以便全面和深入地了解相关内容。
假设不遵循主要的 MVC 体系结构。在开发过程中就会出现很多的问题。最常见的问题是。将过多的任务放到该体系结构的视图部分中。可能存在使用 JSP 标记来运行数据库訪问,或者在 JSP 中进行应用程序的流程控制。这在小规模的应用程序中是比較常见的,可是。随着后期的开发。这样做将会带来问题,由于 JSP 逐步变得越来越难以维护和调试。
类似地,我们也常常看到将视图层构建到业务逻辑的情况。比如,一个常见的问题就是将在构建视图时使用的 XML 解析技术直接应用到业务层。
业务层应该对业务对象进行操作,而不是对与视图相关的特定数据表示进行操作。
然而。只使用适当的组件无法实现应用程序的正确分层。
我们经常见到一些应用程序包括 Servlet、JSP 和 EJB 组件全部这三项,然而,其基本的业务逻辑却是在 Servlet 层实现的,或者应用程序导航是在 JSP 中处理的。您必须对代码进行严格的检查和重构,以确保仅在模型层中处理业务逻辑。在控制器层中进行应用程序导航,而视图应该只关心怎样将模型对象呈现为合适的 HTML 和 Javascript™。
本文中这项建议的涵义应该比原始版本号中的更加清楚。用户接口技术不断地发生着变化,将业务逻辑关联于用户接口,会使得对接口的更改影响到现有的系统。几年之前。Web 应用程序用户接口开发者可能从 Servlet 和 JSP、Struts 和 XML/XSL 转换中进行选择。在那以后。Tiles 和 Faces 很流行,而如今,AJAX 大行其道。假设每当首选的用户接口技术发生了更改就要又一次开发应用程序的核心业务逻辑,那么就糟透了。
2. 不要做反复的工作。
使用常见的、经过证实的框架。如 Apache Struts、JavaServer Faces 和 Eclipse RCP。使用经过证实的模式。
回到我们開始帮助客户使用刚出现的 Java EE 标准的时候,我们发现(和更多人一样)。通过直接使用基础的 Servlet 和 JSP 规范构建 UI 应用程序来开发用户接口开发框架。能够极大地提高开发者工作效率。
因此。很多公司开发了他们自己的 UI 框架,这些框架能够简化接口开发的任务。
随着开放源代码的框架(如 Apache Struts)的出现 [Brown]。我们相信。能够自己主动地和高速地转换到这些新的框架。
我们觉得,使用开放源代码社区支持的框架非常适合于开发者,而且这些框架非常快得到了广泛认可。不仅可用于新的开发,还能够改动现有的应用程序。
但令人感到奇怪的是,事实并不是如此。我们仍可以看到很多公司在维护或甚至开发新的用户接口框架,而这些框架的功能与 Struts 或者 JSF 是全然同样的。
之所以会出现这样的情况,有很多原因:机构惰性。“非我发明”症。不了解更改现有代码的优点、或者甚至傲慢地觉得可以比开放源代码开发者的特定框架做得更好。
然而。这些原因都已经过时了,不可以成为不採用标准框架的借口。Struts 和 JSF 不仅在 Java 社区中得到了广泛认可。并且还受到 WebSphere 执行时和 Rational® 工具套件的全面支持。相同地,在富client领域中,Eclipse RCP(富client平台,Rich Client Platform)获得了广泛的认可。可用于构建独立的富client。虽然不是 Java EE 标准中的一部分。但这些框架如今已成为 Java EE 社区的一部分。并且理应如此。
对于那些由于傲慢而不愿使用现成的 UI 框架的人,应该阅读 [Alur] 和 [Fowler] 中介绍的内容。这两本书具体地描写叙述了企业 Java 应用程序中最经常使用的可重用模式。
从类似于会话 Facade 这样简单的模式(将在后面的建议中讨论)到类似于 Fowler 持久性模式(很多开放源代码的持久性框架对其进行了实现)这样比較复杂的模式。当中的内容体现了 Java 前辈们所积累的智慧。那些不能吸取教训的人必然会重蹈覆辙(假设他们很幸运。可以在第一次失败之后获得重来一次的机会)。他们不得不向哲学家 Santayana 说抱歉。
3. 在应用程序的每一层都使用自己主动单元測试和測试管理。
不要仅仅是測试您的图形用户界面(GUI)。分层的測试使得调试和维护工作变得极其简单。
在过去的几年中,在方法学领域有了相当大的革新。比如新出现的被称为 Agile(如參考资料部分中的
SCRUM [Schwaber] 和极限编程 [Beck1])的轻量级方法如今已经得到了非常普遍的应用。差点儿全部的这些方法中的一个共同特征是它们都提倡使用自己主动的測试工具。这些工具能够帮助开发者用更少的时间进行回归測试,并能够帮助他们避免由于不充分的回归測试造成的错误,因此能够用来提高程序猿的工作效率。
实际上,另一种被称为 Test-First Development [Beck2] 的方法,这样的方法甚至提倡在开发实际的代码之前就先编写单元測试。
然而。在您測试代码之前,您须要将代码切割成一些可測试的片断。一个“大泥球”是难以測试的。由于它不是仅仅实现一个简单的易于识别的功能。假设您的每一个代码片断实现多个方面的功能。将难以測试当中的每一个部分以保证其正确性。
MVC 体系结构(以及 Java EE 中的 MVC 实现)的一个长处就是元素的组件化可以(实际上,相当的简单)对您的应用程序进行单元測试。因此,您可以方便地对实体 Bean、会话 Bean 以及 JSP 独立编写測试用例。而不必考虑其它代码。如今有很多用于 Java EE 測试的框架和工具。这些框架及工具使得这一过程更加简单。比如。JUnit(是一种由 junit.org 开发的开放源码工具)和 Cactus(由 Apache 协会开发的开放源码工具)对于測试 Java EE 组件都很实用。[Hightower] 具体探讨了怎样在 Java EE 中使用这些工具。
虽然全部这些详述了如何彻底地測试您的应用程序。可是我们仍然看到一些人觉得仅仅要他们測试了 GUI(可能是基于 Web 的 GUI,或者是独立的 Java 应用程序),则他们就全面地測试了整个应用程序。仅进行 GUI 測试是不够的。
GUI 測试非常难达到全面的測试,有下面几种原因。
- 使用 GUI 測试非常难彻底地測试到系统的每一条路径,GUI 不过影响系统的一种方式。可能存在后台运算、脚本和各种各样的其它訪问点,这也须要进行測试。然而,它们通常并不具有 GUI。
- GUI 级的測试是一种很粗粒度的測试。这样的測试仅仅是在宏观水平上測试系统的行为,这意味着一旦发现存在问题,则与此问题相关的整个子系统都要进行检查,这使得找出错误将是很困难的事情。
-
GUI 測试通常仅仅有在整个开发周期的后期才干非常好地得到測试,这是由于仅仅有这那个时候 GUI 才得到完整的定义。
这意味着仅仅有在后期才可能发现潜在的错误。
-
一般的开发者可能没有自己主动的 GUI 測试工具。因此。当一个开发者对代码进行更改时,没有一种简单的方法来又一次測试受到影响的子系统。这实际上不利于进行良好的測试。
假设开发者具有自己主动的代码级单元測试工具,开发者就行非常easy地执行这些工具以确保所做的更改不会破坏已经存在的功能。
- 假设加入了自己主动构建功能,则在自己主动构建过程中加入一个自己主动的单元測试工具是很easy的事情。当完毕这些设置以后。整个系统就能够有规律地进行重建。而且回归測试差点儿不须要人的參与。
另外,我们必须强调。使用 EJB 和 Web 服务进行分布式的、基于组件的开发使得測试单个组件变得很必要。假设没有“GUI”须要測试,您就必须进行低级(lower-level)測试。最好以这样的方式開始測试,省得当您将分布式的组件或 Web 服务作为您的应用程序的一部分时,您不得不花费心思又一次进行測试。
总之,通过使用自己主动的单元測试,可以非常快地发现系统的缺陷,而且也易于发现这些缺陷,使得測试工作变得更加系统化,因此总体的质量也得以提高。
4. 依照规范来进行开发,而不是依照顾用server来进行开发。
要将规范熟记于心,假设要背离规范。需经过慎密的考虑后才干够这样做。这是由于当您背离规则的时候,您所做的事情往往并非您应该做的事情。
当您要背离 Java EE 同意您做的事情的时候,这非常easy让使您遭受不幸。我们发现有一些开发者钻研一些 Java EE 同意之外的东西,他们觉得这样做能够“略微”改善 Java EE 的性能,而他们终于仅仅会发现这样做会引起严重的性能问题,或者在以后的移植(从一个厂商到还有一个厂商。或者是更常见的从一个版本号到还有一个版本号)中会出现故障。实际上。这样的移植问题是如此严重,以致 [Beaton] 将此原则称为移植工作的基本最佳实践。
如今有好几个地方假设不直接使用 Java EE 提供的方法肯定会产生问题。
一个常见的样例就是开发者通过使用 JAAS 模块来替代 Java EE 安全性,而不是使用内置的遵循规范的应用server机制来进行验证和授权。要注意不要脱离 Java EE 规范提供的验证机制。假设脱离了此规范,这将是系统存在安全漏洞以及厂商兼容性问题的主要原因。类似地,要使用 Servlet 和 EJB 规范提供的授权机制,而且假设您要偏离这些规范的话。要确保使用规范定义的 API(比如 getCallerPrincipal())作为实现的基础。通过这样的方式,您将可以利用厂商提供的强安全性基础设施,当中。业务要求须要支持复杂的授权规则。
(有关授权的更具体内容。请參见 [Ilechko]。
)
其它常见的问题包含使用不遵循 Java EE 规范的持久性机制(这使得事务管理变得困难)、在Java EE程序中使用不适当的 J2SE 方法(比如线程或 singleton),以及使用您自己的方法解决程序到程序(program-to-program)的通信,而不是使用 Java EE 内在支持的机制(比如 JCA、JMS 或 Web 服务)。当您将一个遵循 Java EE 的server移植到其它的server上。或者移植到同样server的新版本号上,上述的设计选择将会造成无数的问题。使用 Java EE 之外的元素,一般会导致一些细微的可移植性问题。唯一要背离规范的情况是。当一个问题在规范的范围内无法解决的时候。比如,安排运行定时的业务逻辑在 EJB2.1 出现之前是一个问题。
在类似这种情况下,我们建议当有厂商提供的解决方式时就使用厂商提供的解决方式(比如 WebSphere Application Server Enterprise 中的 Scheduler 工具)。而在没有厂商提供的解决方式时就使用第三方提供的工具。
当然。如今的 EJB 规范提供了基于时间的函数,所以我们鼓舞使用这些标准接口。
假设使用厂商提供的解决方式,应用程序的维护以及将其移植到新的规范版本号将是厂商的问题,而不是您的问题。
最后,要注意不要太早地採用新技术。太过于热衷採用还没有集成到 Java EE 规范的其它部分或者还没有集成到厂商的产品中的技术常会带来灾难性的后果。支持是关键的——假设您的厂商不直接支持某种特定的技术,那么您在採用此技术时就应该非常慎重。
有些人(尤其是开发者)过分关注于简化开发过程,忽略了依赖大量本组织之外开发的代码的长期后果,而供应商并不支持这些代码。我们发现,很多项目团队沉迷于新技术(比如最新的开放源码框架)。并不是常快地依赖于它。却没有考虑它对业务带来的实际代价。坦白地说,对于使用您的供应商所提供的产品之外的不论什么技术的决策。都应该由企业组织结构中的各个部门、业务团队和法律团队(或您的环境中的等同机构)细致地进行评审。这与正常的产品购买决策全然同样。毕竟,我们中的大多数人是在解决业务问题,而不是推进技术的发展。
5. 从一開始就计划使用 Java EE 安全性。
启用 WebSphere 安全性。
这使您的 EJB 和 URL 至少能够让全部授权用户訪问。
不要问为什么——照着做就是了。
在与我们合作的客户中。一開始就打算启用 WebSphere Java EE 安全性的顾客是很少的,这一点一直让我们感到惊讶。
据我们预计大约仅仅有 50% 的顾客一開始就打算使用此特性。
比如,我们曾与一些大型的金融机构(银行、代理等等)合作过,他们也没有打算启用安全性。幸运的是,这样的问题在部署之前的检查时就得以解决。
不使用 Java EE 安全性是件危急的事情。如果您的应用程序须要安全性(差点儿全部的应用程序都须要),敢打赌您的开发者可以构建出自己的安全性基础设施,其比您从 Java EE 厂商那里买来的更好。这可不是个好的赌博游戏。
为分布式的应用程序提供安全性是异常困难的。
比如,您须要使用网络安全加密令牌控制对 EJB 的訪问。以我们的经验看来,大多数自己构建的安全性基础设施是不安全的,而且有重大的缺陷,这使产品系统极其脆弱。
(有关更具体的信息,请參考 [Barcia] 的第 18 章。
)
一些不使用 Java EE 安全性的理由包含:操心性能的下降,相信其它的安全性(比如 IBM Tivoli® Access Manager 和 Netegrity SiteMinder)可以代替 Java EE 安全性,或者是不知道 WebSphere Application Server 安全特性及功能。不要陷入这些陷阱之中。
尤其是,虽然像 Tivoli Access Manager 这种产品可以提供优秀的安全特性,可是只其自身不可能保护整个 Java EE 应用程序。这些产品必须与 Java EE 应用server联合起来才可能全面地保护您的系统。
其它一种常见的不使用 Java EE 安全性的原因是,基于角色的模型没有提供足够的粒度訪问控制以满足复杂的业务规则。虽然事实是这种,但这也不应该成为不使用 Java EE 安全性的理由。
相反地。应该将 Java EE 验证及 Java EE 角色与特定的扩展规则结合起来。
假设复杂的业务规则须要做出安全性决策。那就编写对应的代码。其安全性决策要基于能够直接使用的以及可靠的 Java EE 验证信息(用户 ID 和角色)。(有关授权的更具体的信息,请參见 [Ilechko]。)
6. 创建您所知道的。
重复的开发工作将使您可以逐渐地掌握全部的 Java EE 模块。要从创建小而简单的模块開始而不是从一開始就立即涉及到全部的模块。
我们必须承认 Java EE 是庞大的体系。假设一个开发团队仅仅是開始使用 Java EE,这将非常难一下子就能掌握它。在 Java EE 中有太多的概念和 API 须要掌握。在这样的情况下。成功掌握 Java EE 的关键是从简单的步骤開始做起。
这样的方法能够通过在您的应用程序中创建小而简单的模块来得到最好的实现。
假设一个开发团队通过创建一个简单的域模型以及后端的持久性机制(或许使用的是 JDBC),而且对其进行了完整的測试。这会增强他们的自信心,于是他们会使用该域模型去掌握使用 Servlet 和 JSP 的前端开发。
假设一个开发团队发现有必要使用 EJB。他们也会类似地開始在容器管理的持久性 EJB 组件之上使用简单的会话 Facade。或者使用基于 JDBC 的数据訪问对象(JDBC-based Data Access Objects,DAO)。而不是跳过这些去使用更加复杂的构造(比如消息驱动的 Bean 和 JMS)。
这样的方法并非什么新方法。可是非常少有开发团队以这样的方式来培养他们的技能。相反地,多数开发团队因为尝试立即就构建全部的模块,同一时候涉及 MVC 中的视图层、模型层和控制器层,这样做的结果是他们往往会陷入进度的压力之中。他们应该考虑一些敏捷(Agile)开发方法,比如极限编程(XP),这样的开发方法採用一种增量学习及开发方法。在 XP 中有一种称为 ModelFirst [Wiki] 的过程,这个过程涉及到首先构建域模型作为一种机制来组织和实现用户场景。
基本说来。您要构建域模型作为您要实现的用户场景的首要部分,然后在域模型之上构建一个用户界面(UI)作为用户场景实现的结果。这样的方法非常适合让一个开发团队一次仅仅学到一种技术。而不是让他们同一时候面对非常多种情况(或者让他们读非常多书)。这会令他们崩溃的。
还有。对每一个应用程序层反复的开发可能会包括一些适当的模式及最佳实践。假设您从应用程序的底层開始应用一些模式(如数据訪问对象和会话 Facade)。您就不应该在您的JSP和其它视图对象中使用域逻辑。
最后。当您开发一些简单的模块时,在開始的初期就能够对您的应用程序进行性能測试。假设直到应用程序开发的后期才进行性能測试的话,这往往会出现灾难性的后果,正如 [Joines] 所述。
7. 当使用 EJB 组件时。始终使用会话 Facade。
在体系结构合适的情况下,使用本地 EJB。
当使用 EJB 组件时。使用会话 Facade 是一个确认无疑的最佳实践。
实际上。这个通用的实践被广泛地应用到不论什么分布式技术中。包括 CORBA、EJB 和 DCOM。
从根本上讲,您的应用程序的分布“交叉区域”越是底层化,对小块的数据因为多次反复的网络中继造成的时间消耗就越少。要达到这个目的的方法是。创建大粒度的 Facades 对象。这个对象包括逻辑子系统。因而能够通过一个方法调用就能够完毕一些实用的业务功能。这样的方法不但能够降低网络开销。并且在 EJB 内部通过为整个业务功能创建一个事务环境也能够大大地降低对数据库的訪问次数。[Alur] 对这样的模式进行了规范的表示。[Fowler](而且包含除 EJB 之外的情况)和 [Marinescu] 也对其进行了描写叙述(请參见參考资料)。细心的读者会发现。这实际上正是面向服务的体系结构 (SOA) 中的核心原则之中的一个。
EJB 本地接口(从 EJB 2.0 规范開始使用)为共存的 EJB 提供了性能优化方法。
本地接口必须被您的应用程序显式地进行訪问,这须要代码的改变和防止以后配置 EJB 时须要应用程序的改变。
假设您确定 EJB 调用始终是本地的,那么能够充分利用本地 EJB 的优化。然而,会话 Facade 本身的实现(典型样例如无状态会话 Bean)应该设计为远程接口。通过这样的方式,其它的client能够远程地使用 EJB 本身。而不会破坏现有的业务逻辑。由于 EJB 能够同一时候具有本地和远程接口,所以这是全然能够实现的。
为了性能的优化,能够将一个本地接口加入到会话 Facade。这样做利用了这样一个事实,在大多数情况下(至少在 Web 应用程序中),您的 EJB client和 EJB 会共同存在于同一个 Java 虚拟机(JVM)中。第二种情况是,假设会话 Facade 在本地被调用,能够使用 Java EE 应用server配置优化(configuration optimizations),比如 WebSphere 中的“No Local Copies”。
然而,您必须注意到这些可供选择的方案会将交互方法从按值传递(pass-by-value)改变为按引用传递(pass-by-reference)。这可能会在您的代码中产生非常微妙的错误。最好使用本地 EJB,由于对于每一个 Bean 而言。其行为是能够控制的。而不会影响到整个应用server。
假设在您的会话 Facade 中使用远程接口(而不是本地接口)。您也能够将相同的会话 Facade 在 Java EE 1.4 中以兼容的方式作为 Web 服务来配置。(这是由于 JSR 109,Java EE 1.4 中的 Web 服务部署部分。要求使用无状态会话 Bean 的远程接口作为 EJB Web 服务和 EJB 实现的接口。)这样做是值得的。由于这样做能够为您的业务逻辑添加client类型的数量。
8. 使用无状态会话 Bean。而不是有状态会话 Bean。
这样做能够使您的系统更经得起故障转移。使用 HttpSession 存储和用户相关的状态。
以我们的观点来看,有状态会话 Bean 的概念已经过时了。
假设您细致对其考虑一下。一个有状态会话 Bean 实际上与一个 CORBA 对象在体系结构上是全然同样的,无非就是一个对象实例绑定到一个server,而且依赖于server来管理其生命周期。假设server关闭了。这样的对象也就不存在。那么这个 Bean 的client的信息也就不存在。
Java EE 应用server为有状态会话 Bean 提供的故障转移可以解决一些问题,可是有状态的解决方式没有无状态的解决方式易于扩展。比如。在 WebSphere Application Server 中。对无状态会话 Bean 的请求。是通过对部署无状态会话的成员集群进行平衡载入来实现。相反地。Java EE 应用server不能对有状态 Bean 的请求进行平衡载入。
这意味着您的集群中的server的载入过程会是不均衡的。此外,使用有状态会话 Bean 将会再加入一些状态到您的应用server上。这也是不好的做法。
有状态会话 Bean 添加了系统的复杂性,而且在出现问题的情况下使问题变得复杂化。创建健壮的分布式系统的一个关键原则是尽量使用无状态行为。
因此。我们建议对大多数应用程序使用无状态会话 Bean 方法。不论什么在处理时须要使用的与用户相关的状态应该以參数的形式传送到 EJB 的方法中(而且通过使用一种机制如 HttpSession 来存储它)或者从持久性的后端存储(比如通过使用实体 Bean)作为 EJB 事务的一部分来进行检索。在合适的情况下。这个信息能够缓存到内存中。可是要注意在分布式的环境中保存这样的缓存所潜在的挑战性。缓存很适合于仅仅读数据。
总之,您要确保从一開始就要考虑到可扩展性。
检查设计中的全部设想,而且考虑到当您的应用程序要在多个server上执行时。是否也能够正常执行。检查设计中全部的如果,推断如果您的应用程序执行于多个server之上。它们是否依旧成立。这个规则不但适合上述情况的应用程序代码,也适用于如 MBean 和其它管理接口的情况。
避免使用有状态性不仅仅是对 IBM/WebSphere 的建议,这是一个主要的 Java EE 设计原则。
请參见 [Jewell] 的 Tyler Jewell 对有状态 Bean 的批评,其观点和上述的观点是同样的。
9. 使用容器管理的事务。
学习一下 Java EE 中的两阶段提交事务,而且使用这样的方式。而不是开发您自己的事务管理。
容器在事务优化方面差点儿总是比較好的。
使用容器管理的事务(CMT)提供了两个关键的优势(假设没有容器支持这差点儿是不可能的):可组合的工作单元和健壮的事务行为。
假设您的应用程序代码显式地使用了開始和结束事务(或许使用 javax.jts.UserTransaction 或者甚至是本地资源事务),而将来的要求须要组合模块(或许会是代码重构的一部分),这样的情况下往往须要改变事务代码。
比如。假设模块 A 開始了一个数据库事务,更新数据库。随后提交事务,而且有模块 B 做出相同的处理,请考虑一下当您在模块 C 中尝试使用上述两个模块,会出现什么情况呢?如今,模块 C 正在运行一个逻辑动作,而这个动作实际上将调用两个独立的事务。假设模块 B 在运行中失败了,而模块 A 的事务仍然能被提交。
这是我们所不希望出现的行为。
假设,相反地,模块 A 和模块 B 都使用 CMT 的话,模块 C 也能够開始一个 CMT(通常通过配置描写叙述符)。而且在模块 A 和模块 B 中的事务将是同一个事务的隐含部分。这样就不再须要重写复杂的代码了。
假设您的应用程序在同一个操作中须要訪问多种资源。您就要使用两阶段提交事务。比如,假设从 JMS 队列中删除一个消息,而且随后更新基于这条消息的纪录。这时,要保证这两个操作都会运行或都不会运行就变得尤为重要。
假设一条消息已经从队列中被删除,而系统没有更新与此消息相关的数据库中的记录。那么这样的系统是不一致的。一些严重的客户及商业纠纷源自不一致的状态。
我们时常看到一些客户应用程序试图实现他们自己的解决方式。或许会通过应用程序的代码在数据库更新失败的时候“撤销”对队列的操作。我们不提倡这样做。
这样的实现要比您最初的想象复杂得多,而且还有更多的情况(想象一下假设应用程序在运行此操作的过程中突然崩溃的情况)。作为替代的方式,应该使用两阶段提交事务。
假设您使用 CMT。而且在单一的 CMT 中訪问两阶段提交的资源(比如 JMS 和大多数数据库),WebSphere 将会处理全部的复杂工作。它将确保整个事务被运行或者都不被运行,包含系统崩溃、数据库崩溃或其它的情况。事实上如今事务日志中保存着事务状态。
当应用程序訪问多种资源的时候。我们怎么强调使用 CMT 事务的必要性都不为过。假设您所訪问的资源不支持两阶段提交,那么您当然就没有别的选择了,仅仅能使用一种比較复杂的方法,可是您应该尽量避免这样的情况。
10. 将 JSP 作为表示层技术的首选。
仅仅有在须要多种表示输出类型,而且输出类型被单一的控制器及后端支持时才使用 XML/XSLT。
我们常听到一些争论说。为什么您选择 XML/XSLT 而不是 JSP 作为表示层技术,由于 JSP“同意您将模型和视图混合在一起”,而 XML/XSLT 不会有这样的问题。
遗憾的是,这样的观点并不全然正确。或者至少不像白与黑那样分的清楚。实际上。XSL 和 XPath 是编程语言。其实。XSL 是图灵完备的(Turing-complete),虽然它不符合大多数人定义的编程语言。由于它是基于规则的,而且不具备程序猿习惯的控制工具。
问题是既然给予了这样的灵活性,开发者就会利用这样的灵活性。
虽然每一个人都认同 JSP 使开发者easy在视图中增加“类似模型”的行为,而实际上。在 XSL 中也有可能做出一些相同的事情。虽然从 XSL 中进行訪问数据库这样的事情会很困难,可是我们以前见到过一些异常复杂的 XSLT 样式表运行复杂的转换。这实际上是模型代码。
然而,应该选择 JSP 作为首选的表示技术的最主要的原因是。JSP 是如今支持最广泛的、也是最被广泛理解的 Java EE 视图技术。而随着自己定义标记库、JSTL 和 JSP2.0 的新特性的引入,创建 JSP 变得更加easy。而且不须要不论什么 Java 代码,以及能够将模型和视图清晰地分离开。在一些开发环境中(如 IBM Rational Application Developer)增加了对 JSP(包含对调试的支持)的强大支持,而且很多开发者发现使用 JSP 进行开发要比使用 XSL 更加简单,主要是由于 JSP 是基于例程的。而不是基于规则的。
虽然 Rational Application Developer 支持 XSL 的开发。但一些支持 JSP 的图形设计工具及其它特征(尤其在 JSF 这种框架下)使得开发者能够以所见即所得的方式进行 JSP 的开发,而使用 XSL 有时不easy做到。
然而,这并不表示您绝不 应该使用 XSL。在一些情况下,XSL 可以表示一组固定的数据。而且可以基于不同的样式表(请參见 [Fowler])来以不同的方式显示这些数据的能力是显示视图的最佳解决方式。
然而,这仅仅是一种特殊的情况。而不是通用的规则。
假设您仅仅是生成 HTML 来表达每个页面,那么在大多数情况下,XSL 是一种不必要的技术。而且。它给您的开发者所带来的问题远比它所能解决的问题多。
11. 当使用 HttpSession 时,尽量仅仅将当前事务所须要的状态保存在当中。其它内容不要保存在 HttpSession 中。
启用会话持久性。
HttpSessions 对于存储应用程序状态信息是很实用的。其 API 易于使用和理解。
遗憾的是,开发者经常遗忘了 HttpSession 的目的——用来保持暂时的用户状态。它不是随意的数据缓存。我们已经见到过太多的系统为每一个用户的会话放入了大量的数据(达到兆字节)。假设同一时候有 1000 个登录系统的用户,每一个用户拥有 1MB 的会话数据,那么就须要 1G 或者很多其它的内存用于这些会话。
保持这些 HTTP 会话数据较小。
不然的话,您的应用程序的性能将会下降。一个大约比較合适的数据量应该是每一个用户的会话数据在 2K-4K 之间。
这不是一个硬性的规则。
8K 仍然没有问题,可是显然会比 2K 时的速度要慢。一定要注意,不要使 HttpSession 变成数据堆积的场所。
一个常见的问题是使用 HttpSession 缓存一些非常easy再创建的信息。假设有必要的话。因为会话是持久性的,进行不必要的序列化以及写入数据是一种非常奢侈的决定。
相反地,应该使用内存中的哈希表来缓存数据。而且在会话中保存一个对此数据进行引用的键。这样,假设不能成功登录到另外的应用server的话,就能够又一次创建数据。(有关更具体的信息,请參见 [Brown2]。)
当谈及会话持久性时。不要忘记要启用这项功能。假设您没有启用会话持久性,或者server由于某种原因停止了(server故障或正常的维护),则全部此应用server的当前用户的会话将会丢失。
这是件令人很扫兴的事情。用户不得不又一次登录,而且又一次做一些他们以前已经做过的事情。相反地。假设启用了会话持久性,WebSphere 会自己主动将用户(以及他们的会话)移到另外一个应用server上去。用户甚至不知道发生了这种事情。我们以前见到过一些产品系统。由于本地代码中存在令人难以忍受的错误(不是 IBM 的代码!
)而常常崩溃,在这种情况下,上述功能仍然能够执行良好。
12. 充分利用应用server中不须要改动代码的特性。
使用某些特性(如 WebSphere Application Server 缓存和 Prepared Statement 缓存)能够极大地提高性能,而且使得开销最小。
前面的最佳实践 4 清楚地描写叙述了这样一种案例,即关于为什么应该慎重的使用可能改动代码的应用server特定的特性。它使得难以实现可移植性。而且可能给版本号的迁移带来困难。然而,特别是在 WebSphere Application Server 中。有一套应用server特定的特性,您能够而且应该充分地利用它们,由于它们不会改动您的代码。
您应该依照规范来编写代码,但假设您了解这些特性以及怎样正确地使用它们,那么您就能够利用它们显著地改善性能。
作为这个最佳实践的一个演示样例,在 WebSphere Application Server 中。您应该开启动态缓存,而且使用 Servlet 缓存。系统性能能够得到非常大的提高。而开销是最小的。而且不影响编程模型。通过缓存来提高性能的优点是众所周知的事情。遗憾的是,当前的 Java EE 规范没有包含一种用于 Servlet/JSP 缓存的机制。然而,WebSphere 提供了对页面以及片断缓存的支持。这样的支持是通过其动态缓存功能来实现的,而且不须要相应用程序作出不论什么改变。其缓存的策略是声明性的。而且其配置是通过 XML 配置描写叙述符来实现的。因此。您的应用程序不会受到影响,并保持与 Java EE 规范的兼容性和移植性。同一时候还从 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 缓存功能集成在一起。对于繁重的基于读取的工作负荷,通过使用这些功能能够得到很多额外的优点。(请參见參考资料的 [Willenborg] 和 [Bakalova] 中描写叙述的性能的提高。)
作为该原则的还有一个演示样例(我们经常发现客户不使用它,不过由于他们根本不知道它的存在)。在编写 JDBC 代码时能够利用 WebSphere Prepared Statement Cache。
在缺省情况下。当您在 WebSphere Application Server 中使用 JDBC PreparedStatement 时,它将对该语句进行一次编译,然后将其放到缓存中以便再次使用,不仅能够在创建 PreparedStatement 的同一方法中重用,还能够跨程序重用,只要当中使用了同样的 SQL 代码或者还有一个 PreparedStatement。省去又一次编译的步骤能够极大减少调用 JDBC 驱动程序的次数,而且提高应用程序的性能。
要利用这个特性,您仅仅需编写对应的 JDBC 代码以使用 PreparedStatements,而不须要进行不论什么其它工作。
在编写代码时。使用 PreparedStatement 取代常规的 JDBC Statement 类(它使用了纯的动态 SQL),您就能够实现性能的增强,而不会损失不论什么可移植性。
13. 充分利用现有的环境。
提供一个 Java EE EAR 和可配置的安装脚本。而不是黑盒二进制安装程序。
在大多数的实际场景中,大量的 WebSphere Application Server 用户在同样的共享单元中运行多个应用程序。
这意味着。如果您提供一个须要安装的应用程序,那么它必须可以合理地安装到现有的基础设施中。这意味两个方面:首先。您必须限制关于环境的如果的数目,而且由于您无法预料到全部的情况。所以您的安装过程必须是可见的。
这里所说的可见是指。不应该提供二进制可运行文件形式的安装程序。
运行安装任务的管理员须要清楚安装过程对他们的单元所进行的操作。为了实现这样的方式,您应该提供一个 EAR 文件(或者一组 EAR 文件)以及相关的文档和安装脚本。
这些脚本应该具有可读性。以便安装程序可以知道它们须要运行的操作,并对脚本的内容进行验证以确保不会运行不论什么危急的操作。
在有些情况下。脚本并不合适,用户可能须要使用一些曾用过的其它方法来安装 EAR。这表示您必须记录安装程序所完毕的工作!
14. 充分利用应用server环境所提供的服务质量。
设计可使用 WebSphere Application Server Network Deployment 集群的应用程序。
我们已经介绍了利用 WebSphere Application Server 安全和事务支持的重要性。另一个更重要的、经常被我们忽视的问题。即集群。须要将应用程序设计为可以执行于集群的环境。大多数实际的环境须要通过集群来实现可扩展性和可靠性。无法进行集群的应用程序非常快会导致灾难的出现。
与集群紧密相关的是支持 WebSphere Application Server Network Deployment。假设您正在构建一个应用程序并打算将它卖给其它人,请确保您的应用程序能够执行于 WebSphere Application Server Network Deployment,而不不过单个server版本号。
15. 利用 Java EE,不要欺骗。
致力于构建真正利用 Java EE 功能的 Java EE 应用程序。
有件很烦人的事情我们曾多次遇到过,某个应用程序声称能够执行于 WebSphere 中,但它并非一个真正的 WebSphere 应用程序。我们曾见过几个这种演示样例,当中有一小段代码(可能是一个 Servlet)位于 WebSphere Application Server 中。而其余全部的应用程序逻辑实际上位于单独的进程中。比如一个以 Java、C、C++ 或其它语言(没有使用 Java EE)编写的守护进程负责完毕实际的工作。这并非一个真正的 WebSphere Application Server 应用程序。对于这种应用程序。WebSphere
Application Server 所提供的差点儿全部的服务质量都不可用。对于那些觉得这是 WebSphere Application Server 应用程序的人来说,他们会突然的醒悟过来。原来并不是如此。
16. 安排进行版本号更新。
更改是在所难免的。安排新的发行版和修复程序更新,以便您的客户可以获得最新的版本号。
WebSphere Application Server 在不断地发展,所以 IBM 定期地给出 WebSphere Application Server 的修复程序,这是非常正常的,而且 IBM 还定期地公布新的主要版本号。您须要为此做好安排。这会影响到两类开发组织:内部开发者和第三方应用程序供应商。主要的问题是同样的,但对两者的影响则有所不同。
首先考虑修复程序。IBM 定期公布建议更新,以修复产品中已发现的错误。
虽然不太可能始终执行于最新的级别,但请注意。不要隔得太久。那么到底“隔多久”是能够接受的呢?对于这个问题没有什么正确的答案,可是您应该安排好对几个月内的发行版进行修复级别更新。是的。这表示一年要更新好几次。内部开发者能够忽略某些修复级别,一次仅支持一个修复级别,以减少測试成本。应用程序供应商则没有这么幸运。假设您是应用程序供应商。那么您同一时候须要支持多种修复级别。以便您的客户能够将您的软件与其它软件一同执行。假设您仅支持一种修复级别,那么非常可能无法找到同一时候兼容于多种产品的修复级别。实际上对于供应商而言,最好的方法是使用支持“向上兼容修复程序”的模型。
IBM 使用了这样的方法来支持所集成的来自其它供应商的产品(如 Oracle®、Solaris™ 等等)。
有关更具体的信息,请參考我们的支持策略。
以下再考虑一下主要版本号更新。IBM 定期地公布新的主要发行版,当中对我们的产品进行了基本的功能更新。我们临时继续支持旧的主要发行版,但不会太久。这意味着您必须安排从一个主要发行版转到还有一个主要发行版。这是不可避免的,而且应该在您的成本模型中加以考虑。假设您是供应商。这意味着您必须常常地对您的产品进行更新。以支持新的 WebSphere Application Server 版本号。否则您的客户将停滞于不受支持的 IBM 产品,我们曾多次碰到过这样的情况。假设您正从供应商处购买产品。我们鼓舞您要留心您的供应商。以确保他们承诺支持 IBM 产品新的版本号。
停滞于不受支持的软件是一种很危急的情况。
17. 在代码中全部关键的地方,使用标准的日志框架记录程序的状态。
这包含异常处理程序。使用像 JDK 1.4 Logging 或 Log4J 这种日志框架。
有些时候,日志记录是最乏味的工作。降低了编程的价值,可是这样做能够降低调试的时间。并尽快地完毕对应的任务。依据一般的经验。在每一个过渡的地方,须要进行日志记录。当您将參数从一个方法传递到还有一个方法,或从一个类传递到还有一个类。须要进行日志记录。
在对一个对象进行某种转换时。须要进行日志记录。在碰到不解之处时。须要进行日志记录。
在决定了进行日志记录之后,须要选择一种合适的框架。
实际上有很多选择。可是我们偏爱 JDK 1.4 Trace API。由于它们已全面地集成到了 WebSphere Application Server 跟踪子系统中,而且是基于标准的。
18. 在完毕对应的任务后。请始终进行清理。
假设您从池中获取了一个对象,请始终确保将其返回到池中。
不管执行于开发、測试或生产环境中,我们发现 Java EE 应用程序最常见的错误之中的一个是内存泄漏。
绝大部分情况是由于开发者忘了关闭连接(大多数情况下是 JDBC 连接)或将对象返回到池中。对于不论什么须要显式关闭的或须要返回到池中的对象,请确保进行了这种操作。
不要编写出这样糟糕的代码。
19. 在开发和測试过程中遵循严格的程序。
这包含採用和遵循软件开发方法学。
大型系统的开发是非常困难的。所以应该十分慎重。
可是,我们经常发现一些团队疏于管理、或者不能全心全意地遵循相关的开发方法(这些方法可能不适用于他们正在进行的开发类型)、或者他们并没有非常好地理解这一点。
最为糟糕的可能是尝试每一个月更换不同的开发方法。在单个项目的生命周期中,一个团队从 RUP 改变为 XP。以及一些其它敏捷方法。
总之,对于大多数团队而言。仅仅要团队成员可以非常好地理解、严格地运行、并依据特定的技术本质和使用该方法的团队进行适当的调整。那么差点儿不论什么一种方法都是有效的。对于那些尚未採用不论什么方法、或者那些不可以全然地利用所选方法的团队,我们建议他们參考一些优秀的著作。如 [Jacobson]、[Beck1] 或 [Cockburn]。
还有一个有价值的信息来源是近期发布的用于 Eclipse Process Framework [Eclipse] 的 OpenUP 插件。对于这个已经介绍过的主题,我们不想做过多的反复。建议读者參考 [Hambrick] 和 [Beaton2](请參见參考资料)。
结束语
在这个简短的摘要中。我们已经向您介绍了 Java EE 中的核心模式和最佳实践。它们使得 Java EE 开发成为一种可管理的过程。虽然我们并没有给出全部在实践中使用这些模式的必要细节,可是我们希望可以给您足够的指点和指导。以帮助您决定下一步要做什么。
致谢
感谢全部最初提出这些模式和最佳实践的人(以及以下提到的人),此外还要感谢 John Martinek、Paul Ilechko、Bill Hines、Dave Artus 和 Roland Barcia 对本文的批阅。
參考资料
- [Alur] Deepak Alur、John Crupi 和 Danny Malks 合著的 Core J2EE Patterns(第二版),Addison-Wesley,2003 年
- [Bakalova] R. Bakalova 等撰写的 WebSphere Dynamic Cache:Improving WebSphere Performance。IBM Systems Journal。2004 年第 2 期第 43 卷
- [Barcia] Roland Barcia 等撰写的 IBM WebSphere:Enterprise Deployment and Advanced Configuration。IBM Press。2004 年
- [Beck1] Kent Beck 撰写的 Extreme Programming Explained:Embrace Change。Addison-Wesley,1999 年
- [Beck2] Kent Beck 撰写的 Test Driven Development by Example,Addison-Wesley,2002 年
- [Beaton] Wayne Beaton 撰写的 移植到 IBM WebSphere Application Server,第 I 部分: 设计利于移植的软件,IBM developerWorks
- [Beaton2] Wayne Beaton 撰写的 理想的 WebSphere 开发环境,IBM developerWorks
- [Brown] Kyle Brown 等撰写的 Enterprise Java Programming with IBM WebSphere,第二版,Addison-Wesley。2003 年
- [Brown 2] Kyle Brown 和 Keys Botzum 撰写的 Improving HttpSession Performance with Smart Serialization,IBM developerWorks
- [Cockburn] Alistair Cockburn 和 Crystal Clear 撰写的 A Human-Powered Methodology for Small Teams。Addison-Wesley。2004 年
- [Cox] Stan Cox 和 Brian K. Martin 撰写的 Exploiting Dynamic Caching in WAS 5.0, Part1,e-Pro Magazine(2003 年 7/8 月)
- [Eclipse] Eclipse Process Framework
- [Fowler] Martin Fowler 撰写的 Patterns of Enterprise Application Architecture。Addison-Wesley,2002 年
- [Hambrick] Geoff Hambrick 等撰写的 为什么非功能性需求非常重要?,IBM WebSphere 开发人员技术期刊,2006 年
- [Ilechko] Paul Ilechko 撰写的 ilechko。IBM developerWorks,2006 年
- [Jacobson] Ivar Jacobson 等撰写的 Unified Software Development Process。Addison-Wesley。1999 年
- [Jewell] Tyler Jewell 撰写的 Stateful Session Beans:Beasts of Burden,OnJava.com
- [Joines] Stacy Joines、Ken Hygh 和 Ruth Willenborg 撰写的 Performance Analysis for Java Websites。Addison-Wesley,2002 年
- [Marinescu] Floyd Marinescu 撰写的 EJB Patterns。John Wiley & Sons,2002 年
- [Schwaber] Ken Schwaber 和 Michael Beedle 撰写的 Agile Software Development with SCRUM,Prentice-Hall。2001 年
- [Wiki] Wiki Web, http://c2.com/cgi-bin/wiki?ModelFirst
- [Willenborg] R. Willenborg、K. Brown 和 G. Cuomo 撰写的 Designing WebSphere Application Server for performance:An evolutionary approach,IBM Systems Journal,2004 年第 2 期第 43 卷