虽然许多文章曾经讨论过J2EE最佳实践。那么,为什么我还要再写一篇文章呢?本文究竟与以前的文章有何不同或者说比其他文章好在哪呢?
首先,本文的目标读者是正在从事技术工作的架构师。为了避免浪费大家的才智,我会避免讲述一些陈腐的最佳实践,例如“日常构建(build daily)”、“测试一切(test everything)”和“经常集成( integrate often)。 任何具有称职架构师的项目都有分工明确的、定义良好的团队结构。他们还为进行编码检查、构建代码(每日或在需要时)、进行测试(单元、集成和系统的)、部署和配置/释放管理而具备已记录的过程。
其次,我将跳过通常吹捧的最佳实践,例如“基于接口的设计”、“使用著名的设计模型”以及“使用面向服务的架构”等。相反,我将集中讲述我曾学过并且使用了若干年的6(不是很多)个方面的in-the-trench课程。最后,本文的目的是让您思考一下自己的架构,提供工作代码示例或者解决方案超出了本文的范围。下面就让我介绍一下这6课:
1、切勿绕过服务器端验证
2、安全并非是附加物
3、国际化(I18N)不再是纸上谈兵
4、在MVC表示中避免共同的错误
5、不要被POJO束缚住手脚
6、数据访问并不能托管O/R映射
第1课:切勿绕过服务器端验证
作为一位软件顾问,我曾有机会不但设计并实现了Web应用程序,而且还评估/审核了许多Web应用程序。在复杂的、并且用JavaScript客户端封装的应用程序内,我经常遇到对用户输入信息执行大量检查的Web页面。即使HTML元素具有数据有效性的属性也如此,例如MAXLENGTH。只有在成功验证所有输入信息后,才能提交HTML表单。结果,一旦服务器端收到通知表单(请求),便恰当地执行业务逻辑。
在此,您发现问题了么?开发人员已经做了许多重要的假设。例如,他们假设所有的Web应用程序用户都同样诚实。开发人员还假设所有用户将总是使用他们测试过的浏览器访问Web应用程序。还有很多其他的假设。这些开发人员忘记了利用可以免费得到的工具,通过命令行很容易地模拟类似浏览器的行为。事实上,通过在浏览器窗口中键入适当的URL,您可以发送任何“posted”表单,尽管如此,通过禁用这些页面的GET请求,您很容易地阻止这样的“表单发送”。但是,您不能阻止人们模拟甚至创建他们自己的浏览器来入侵您的系统。
根本的问题在于开发人员不能确定客户端验证与服务器端验证的主要差别。两者的主要差别不在于验证究竟发生在哪里,例如在客户端或者在服务器端。主要的差别在于验证背后的目的不同。
客户端验证仅仅是方便。执行它可为用户提供快速反馈??使应用程序似乎做出响应,给人一种运行桌面应用程序的错觉。
另一方面,服务器端验证是构建安全Web应用程序必需的。不管在客户端一侧输入的是什么,它可以确保客户端送往服务器的所有数据都是有效的。
因而,只有服务器端验证才可以提供真正应用程序级的安全。许多开发人员陷入了错误感觉的圈套:只有在客户端进行所有数据的验证才能确保安全。下面是说明此观点的一个常见的示例:
一个典型的登录页面拥有一个用来输入用户名的文本框和一个输入密码的文本框。在服务器端,某人在接收servlet中可能遇到一些代码,这些代码构成了下面形式的SQL查询:
"SELECT * FROM SecurityTable WHERE username = @#" + form.getParameter("username") + "@# AND password = @#" + form.getParameter("password") + "@#;",并执行这些代码。如果查询在结果集的某一行返回,则用户登录成功,否则用户登录失败。
第一个问题是构造SQL的方式,但现在让我们暂时忽略它。如果用户在用户名中输入“Alice@#--”会怎样呢?假设名为“Alice”的用户已经在SecurityTable中,这时此用户(更恰当的说法是黑客)成功地登录。我将把找出为什么会出现这种情况的原因做为留给您的一道习题。
许多创造性的客户端验证可以阻止一般的用户从浏览器中这样登录。但对于已经禁用了JavaScript的客户端,或者那些能够使用其他类似浏览器程序直接发送命令(HTTP POST和GET命令)的高级用户(或者说黑客)来说,我们又有什么办法呢?服务器端验证是防止这种漏洞类型所必须的。这时,SSL、防火墙等都派不上用场了。
第2课:安全并非是附加物
如第1课所述,我曾有幸研究过许多Web应用程序。我发现所有的JavaServer Page(JSP)都有一个共同的主题,那就是具有类似下面伪代码的布局:
<%
User user =
session.getAttribute("User");
if(user == null)
{
// redirect to
// the logon page…
}
if(!user.role.equals("manager"))
{
// redirect to the
// "unauthorized" page…
}
%>
<!-
HTML, JavaScript, and JSP
code to display data and
allow user interaction -->
如果项目使用诸如Struts这样的MVC框架,所有的Action Bean都会具有类似的代码。尽管最后这些代码可能运行得很好,但如果您发现一个bug,或者您必须添加一个新的角色(例如,“guest”或者“admin”),这就会代表一场维护恶梦。
此外,所有的开发人员,不管您多年轻,都需要熟悉这种编码模式。当然,您可以用一些JSP标签来整理JSP代码,可以创建一个清除派生Action Bean的基本Action Bean。尽管如此,由于与安全相关的代码会分布到多个地方,所以维护时的恶梦仍旧存在。由于Web应用程序的安全是强迫建立在应用程序代码的级别上(由多个开发人员),而不是建立在架构级别上,所以Web应用程序还是很可能存在弱点。
很可能,根本的问题是在项目接近完成时才处理安全性问题。最近作为一名架构师,我曾在一年多的时间里亲历了某一要实现项目的6个版本,而直到第四版时我们才提到了安全性??即使该项目会将高度敏感的个人数据暴露于Web上,我们也没有注意到安全性。为了更改发布计划,我们卷入了与项目资助人及其管理人员的争斗中,以便在第一版中包含所有与安全相关的功能,并将一些“业务”功能放在后续的版本中。最终,我们赢得了胜利。而且由于应用程序的安全性相当高,能够保护客户的私有数据,这一点我们引以为荣,我们的客户也非常高兴。
遗憾的是,在大多数应用程序中,安全性看起来并未增加任何实际的商业价值,所以直到最后才解决。发生这种情况时,人们才匆忙开发与安全相关的代码,而丝毫没有考虑解决方案的长期可维护性或者健壮性。忽视该安全性的另一个征兆是缺乏全面的服务器端验证,如我在第1课中所述,这一点是安全Web应用程序的一个重要组成部分。
记住:J2EE Web应用程序的安全性并非仅仅是在Web.xml 和ejb-jar.xml文件中使用合适的声明,也不是使用J2EE技术,如Java 认证和授权服务(Java Authentication and Authorization Service,JAAS)。而是经过深思熟虑后的设计,且实现一个支持它的架构。
第3课:国际化(I18N)不再是纸上谈兵
当今世界的事实是许多英语非母语的人们将访问您的公共Web应用程序。随着电子政务的实行,由于它允许人们(某个国家的居民)在线与政府机构交互,所以这一点特别真实。这样的例子包括换发驾照或者车辆登记证。许多第一语言不是英语的人们很可能将访问这样的应用程序。国际化(即:“i18n”,因为在“internationalization”这个单词中,字母i和字母n之间一共有18个字母)使得您的应用程序能够支持多种语言。
显然,如果您的JSP 页面中有硬编码的文本,或者您的Java代码返回硬编码的错误消息,那么您要花费很多时间开发此Web应用程序的西班牙语版本。然而,在Web应用程序中,为了支持多种语言,文本不是惟一必须“具体化”的部分。因为许多图像中嵌有文字,所以图形和图像也应该是可配置的。在极端的情况下,图像(或者颜色)在不同的文化背景中可能有完全不同的意思。类似地,任何格式化数字和日期的Java代码也必须本地化。但问题是:您的页面布局可能也需要更改。
例如,如果您使用HTML表格来格式化和显示菜单选项、应用程序题头或注脚,则您可能必须为每一种支持的语言更改每一栏的最小宽度和表格其他可能的方面。为了适应不同的字体和颜色,您可能必须为每一种语言使用单独的样式表。
显然,现在创建一个国际化的Web应用程序面临的是架构挑战而不是应用程序方面的挑战。一个架构良好的Web应用程序意味着您的JSP页面和所有与业务相关的(应用程序特有的)Java代码都不知不觉地选择了本地化。要记住的教训是:不要因为Java、J2EE支持国际化而不考虑国际化。您必须从第一天起就记住设计具有国际化的解决方案。
第4课:在MVC表示中避免共同的错误
J2EE开发已经足够成熟,在表示层,大多数项目使用MVC架构的某些形式,例如Struts。在这样的项目中,我常见到的现象是对MVC模式的误用。下面是几个示例。
常见的误用是在模型层(例如,在Struts的Action Bean中)实现了所有的业务逻辑。不要忘了,表示层的模型层仍然是表示层的一部分。使用该模型层的正确方法是调用适当的业务层服务(或对象)并将结果发送到视图层(view layer)。用设计模式术语来说,MVC表示层的模型应该作为业务层的外观(Fa?ade)来实现。更好的方法是,使用核心J2EE模式(Core J2EE Patterns)中论述到的Business Delegate模式。这段自书中摘录的内容精彩地概述了将您的模型作为Business Delegate来实现的要点和优点:
Business Delegate起到客户端业务抽象化的作用。它抽象化,进而隐藏业务服务的实现。使用Business Delegate,可以降低表示层客户端和系统的业务服务.之间的耦合程度。根据实现策略不同,Business Delegate可以在业务服务API的实现中,保护客户端不受可能的变动性影响。这样,在业务服务API或其底层实现变化时,可以潜在地减少必须修改表示层客户端代码的次数。
另一个常见的错误是在模型层中放置许多表示类型的逻辑。例如,如果JSP页面需要以指定方式格式化的日期或者以指定方式排序的数据,某些人可能将该逻辑放置在模型层,对该逻辑来说,这是错误的地方。实际上,它应该在JSP页面使用的一组helper类中。当业务层返回数据时,Action Bean应该将数据转发给视图层。这样,无需创建模型和视图之间多余的耦合,就能够灵活支持多个视图层(JSP、Velocity、XML等)。也使视图能够确定向用户显示数据的最佳方式。
最后,我见过的大多数MVC应用程序都有未充分应用的控制器。例如,绝大多数的Struts应用程序将创建一个基本的Action类,并完成所有与安全相关的功能。其他所有的Action Bean都是此基类的派生类。这种功能应该是控制器的一部分,因为如果没有满足安全条件,则首先调用不应该到达Action Bean(即:模型)。记住,一个设计良好的MVC架构的最强大功能之一是存在一个健壮的、可扩展的控制器。您应该利用该能力以加强自己的优势。
第5课:不要被POJO束缚住手脚
我曾目睹许多项目为了使用Enterprise JavaBean而使用Enterprise JavaBean。因为EJB似乎给项目带来优越感和妄自尊大的表现,所以有时它是显酷的要素(coolness factor)。而其他时候,它会使J2EE和EJB引起混淆。记住,J2EE和EJB不是同意词。EJB只是J2EE 的一部分,J2EE 是包含JSP、servlet、Java 消息服务(JMS)、Java数据库连接(JDBC)、JAAS、 Java管理扩展(JMX)和EJB在内的一系列技术,同样也是有关如何共同使用这些技术建立解决方案的一组指导原则和模式。
如果在不需要使用EJB的情况下使用EJB,它们可能会影响程序的性能。与老的Web服务器相比,EJB一般对应用服务器有更多的需求。EJB提供的所有增值服务一般需要消耗更大的内存和更多的CPU时间。许多应用程序不需要这些服务,因此应用服务器要与应用程序争夺资源。
在某些情况下,不必要地使用EJB可能使应用程序崩溃。例如,最近我遇到了一个在开源应用服务器上开发的应用程序。业务逻辑封装在一系列有状态会话bean(EJB)中。开发人员为了在应用服务器中完全禁用这些bean的“钝化”费了很大的劲。客户端要求应用程序部署在某一商用应用服务器上,而该服务器是客户端技术栈的一部分。该应用服务器却不允许关闭“钝化”功能。事实上,客户端不想改变与其合作的应用服务器的设任何置。结果,开发商碰到了很大的麻烦。(似乎)有趣的事情是开发商自己都不能给出为什么将代码用EJB(而且还是有状态会话bean)实现的好理由。不仅仅是开发商会遇到性能问题,他们的程序在客户那里也无法工作。
在Web应用程序中,无格式普通Java 对象(POJO)是EJB强有力的竞争者。POJO是轻量级的,不像EJB那样负担额外的负担。在我看来,对许多EJB的优点,例如对象入池,估计过高。POJO是您的朋友,不要被它束缚住手脚。
第6课:数据访问并不能托管O/R映射
我曾参与过的所有Web应用程序都向用户提供从其他地方存取的数据,并且因此需要一个数据访问层。这并不是说所有的项目都需要标识并建立这样一个层,这仅仅说明这样层的存在不是隐含的就是明确的。如果是隐含的数据层,数据层是业务对象(即:业务服务)层的一部分。这适用于小型应用程序,但通常与大一些项目所接受的架构指导原则相抵触。
总之,数据访问层必须满足或超出以下四个标准:
具有透明性
业务对象在不知道数据源实现的具体细节情况下,可以使用数据源。由于实现细节隐藏在数据访问层的内部,所以访问是透明的。
易于迁移
数据访问层使应用程序很容易迁移到其他数据库实现。业务对象不了解底层的数据实现,所以迁移仅仅涉及到修改数据访问层。进一步地说,如果您正在部署某种工厂策略,您可以为每个底层的存储实现提供具体的工厂实现。如果是那样的话,迁移到不同的存储实现意味着为应用程序提供一个新的工厂实现。
尽量减少业务对象中代码复杂性
因为数据访问层管理着所有的数据访问复杂性,所以它可以简化业务对象和使用数据访问层的其他数据客户端的代码。数据访问层,而不是业务对象,含有许多与实现相关的代码(例如SQL语句)。这样给开发人员带来了更高的效率、更好的可维护性、提高了代码的可读性等一系列好处。
把所有的数据访问集中在单独的层上
由于所有的数据访问操作现在都委托给数据访问层,所以您可以将这个单独的数据访问层看做能够将应用程序的其他部分与数据访问实现相互隔离的层。这种集中化可以使应用程序易于维护和管理。
注意:这些标准都不能明确地调出对O/R(对象到关系)映射层的需求。O/R映射层一般用O/R映射工具创建,它提供对象对关系数据结构的查看和感知(look-and-feel)。在我看来,在项目中使用O/R映射与使用EJB类似。在大多数情况下,并不要求它。对于包含中等规模的联合以及多对多关系的关系型数据库来说,O/R映射会变得相当复杂。由于增加O/R 映射解决方案本身的内在复杂性,例如延迟加载(lazy loading)、高速缓冲等,您将为您的项目带来更大的复杂性(和风险)。
为了进一步支持我的观点,我将指出按照Sun Microsystem所普及的实体Bean(O/R映射的一种实现)的许多失败的尝试,这是自1.0版以来一直折磨人的难题。在SUN的防卫措施中,一些早期的问题是有关EJB规范的开发商实现的。这依次证明了实体Bean规范自身的复杂性。结果,大多数J2EE架构师一般认为从实体Bean中脱离出来是一个好主意。
大多数应用程序在处理他们的数据时,只能进行有限次数的查询。在这样的应用程序中,访问数据的一种有效方法是实现一个数据访问层,该层实现执行这些查询的一系列服务(或对象、或API)。如上所述,在这种情况下,不需要O/R映射。当您要求查询灵活性时,O/R映射正合适,但要记住:这种附加的灵活性并不是没有代价的。
就像我承诺的那样,在本文中,我尽量避免陈腐的最佳实践。相反,关于J2EE项目中每一位架构师必须做出的最重要的决定,我集中讲解了我的观点。最后,您应该记住:J2EE并非某种具体的技术,也不是强行加入到解决方案中的一些首字母缩写。相反,您应该在适当的时机,恰当的地方,使用合适的技术,并遵循J2EE的指导原则和J2EE中所包含的比技术本身重要得多的实践。
JAVA 12个最重要的J2EE最佳实践
1、始终使用 MVC 框架。
2、在每一层都应用自动单元测试和测试管理。
3、按照规范来进行开发,而不是按照应用服务器来进行开发。
4、从一开始就计划使用 J2EE 安全性。
5、创建您所知道的。
6、当使用 EJB 组件时,始终使用会话 Facades。
7、使用无状态会话 bean,而不是有状态会话 bean.
8、使用容器管理的事务。
9、将 JSP 作为表示层的首选。
10、当使用 HttpSession 时,尽量只将当前事务所需要的状态保存其中,其他内容不要保存在 HttpSession 中。
11、在 WebSphere 中,启动动态缓存,并使用 WebSphere servlet 缓存机制。
12、为了提高程序员的工作效率,将 CMP 实体 bean 作为 O/R 映射的首选解决方案。
1. 始终使用 MVC 框架。
MVC 框架可以将业务逻辑(Java beans 和 EJB 组件)、控制器逻辑(Servlets/Struts 动作)、表示层(JSP、XML/XSLT)清晰地分离开来。良好的分层可以带来许多好处。
MVC 框架对于成功使用 J2EE 是如此重要,以致没有其他最佳实践可以与其相提并论。模型-视图-控制器(MVC)是设计 J2EE 应用程序的基础。MVC 将您的程序代码简单地划分下面几个部分:
负责业务逻辑的代码(即模型——通常使用 EJB 或者普通的 Java 对象来实现)。
负责用户界面显示的代码(即视图——通常通过 JSP 及标记库来实现,有时也使用 XML 和 XSLT 来实现)。
负责应用程序流程的代码(即控制器——通常使用 Java Servlet 或像 Struts 控制器这样的类来实现)。
如果您不遵循基本的 MVC 框架,在开发过程中就会出现许多的问题。最常见的问题就是在视图部分添加了太多的成分,例如,可能存在使用 JSP 标记来执行数据库访问,或者在 JSP 中进行应用程序的流程控制,这在小规模的应用程序中是比较常见的,但是,随着后期的开发,这样做将会带来问题,因为 JSP 逐步变得越来越难以维护和调试。
类似地,我们也经常看到将视图层构建到业务逻辑的情况。例如,一个常见的问题就是将在构建视图时使用的 XML 解析技术直接应用到业务层。业务层应该对业务对象——而不是绑定到视图的特定数据表示进行操作。
然而,只是具有合适的组件并不一定意味着可以使您的应用程序得到合适的分层。我们常常见到一些应用程序包含 servlet、JSP 和 EJB 组件所有这三项,然而,其主要的业务逻辑却是在 servlet 层实现的,或者应用程序导航是在 JSP 中处理的。您必须进行严格的代码检查并重构您的代码,以确保应用程序的业务逻辑只在模型层(Model layer)进行处理,应用程序导航只通过控制器层(Controller layer)进行处理,而您的视图(Views)只是将传递过来的模型对象以 HTML 及 JavaScript 的形式表示出来。
2. 在应用程序的每一层都使用自动单元测试和测试管理。
不要只是测试您的图形用户界面(GUI)。分层的测试使测试及维护工作变得极其简单。
在过去的几年中,在方法学领域有了相当大的革新,例如新出现的被称为 Agile(例如 SCRUM [Schwaber] 和极限编程 [Beck1])的轻量级方法现在已经得到了很普遍的应用。几乎所有的这些方法中的一个共同的特征是它们都提倡使用自动的测试工具,这些工具可以帮助开发人员用更少的时间进行回归测试 (regression testing),并可以帮助他们避免由于不充分的回归测试造成的错误,因此可以用来提高程序员的工作效率。实际上,还有一种被称为 Test-First Development [Beck2] 的方法,这种方法甚至提倡在开发实际的代码之前就先编写单元测试。然而,在您测试代码之前,您需要将代码分割成一些可测试的片断。一个“大泥球”是难以测试的,因为它不是只实现一个简单的易于识别的功能。如果您的每个代码片断实现多个方面的功能,这样的代码将难以保证其完全的正确性。
MVC 框架(以及 J2EE 中的 MVC 实现)的一个优点就是元素的组件化能够(实际上,相当的简单)对您的应用程序进行单元测试。因此,您可以方便地对实体 bean、会话 bean 以及 JSP 独立编写测试用例,而不必考虑其他的代码。现在有许多用于 J2EE 测试的框架和工具,这些框架及工具使得这一过程更加简单。例如,JUnit(是一种由 junit.org 开发的开放源代码工具)和 Cactus(由 Apache 开发的开放源代码工具)对于测试 J2EE 组件都非常有用。[Hightower] 详细探讨了如何在 J2EE 中使用这些工具。
尽管所有这些详述了怎样彻底地测试您的应用程序,但是我们仍然看到一些人认为只要他们测试了 GUI(可能是基于 Web 的 GUI,或者是独立的 Java 应用程序),则他们就全面地测试了整个应用程序。GUI 测试很难达到全面的测试,有以下几种原因。首先,使用 GUI 测试很难彻底地测试到系统的每一条路径,GUI 仅仅是影响系统的一种方式,可能存在后台运算、脚本和各种各样的其他访问点,这也需要进行测试。然而,它们通常并不具有 GUI。第二,GUI 级的测试是一种非常粗粒度的测试。这种测试只是在宏观水平上测试系统的行为。这意味着一旦发现存在问题,则与此问题相关的整个子系统都要进行检查,这使得找出 bug(缺陷)将是非常困难的事情。第三,GUI 测试通常只有在整个开发周期的后期才能很好地得到测试,这是因为只有这那个时候 GUI 才得到完整的定义。这意味着只有在后期才可能发现潜在的 bug。第四,一般的开发人员可能没有自动的 GUI 测试工具。因此,当一个开发人员对代码进行更改时,没有一种简单的方法来重新测试受到影响的子系统。这实际上不利于进行良好的测试。如果开发人员具有自动的代码级单元测试工具,开发人员就能够很容易地运行这些工具以确保所做的更改不会破坏已经存在的功能。最后,如果添加了自动构建功能,则在自动构建过程中添加一个自动的单元测试工具是非常容易的事情。当完成这些设置以后,整个系统就可以有规律地进行重建,并且回归测试几乎不需要人的参与。
另外,我们必须强调,使用 EJB 和 Web 服务进行分布式的、基于组件的开发使得测试单个的组件变得非常必要。如果没有“GUI”需要测试,您就必须进行低级(lower-level)测试。最好以这种方式开始测试,省得当您将分布式的组件或 Web 服务作为您的应用程序的一部分时,您不得不花费心思重新进行测试。
总之,通过使用自动的单元测试,能够很快地发现系统的缺陷,并且也易于发现这些缺陷,使得测试工作变得更加系统化,因此整体的质量也得以提高。
3. 按照规范来进行开发,而不是按照应用服务器来进行开发。
要将规范熟记于心,如果要背离规范,要经过慎密的考虑后才可以这样做。这是因为当您背离规则的时候,您所做的事情往往并不是您应该做的事情。
当您要背离 J2EE 可以允许您做的事情的时候,这很容易让使您遭受不幸。我们发现有一些开发人员钻研一些 J2EE 允许之外的东西,他们认为这样做可以“稍微”改善J2EE的性能,而他们最终只会发现这样做会引起严重的性能问题,或者在以后的移植(从一个厂商到另一个厂商,或者是更常见的从一个版本到另一个版本)中会出现问题。实际上,这种移植问题是如此严重,以致 [Beaton] 将此原则称为移植工作的基本最佳实践。
现在有好几个地方如果不直接使用 J2EE 提供的方法肯定会产生问题。一个常见的例子就是开发人员通过使用 JAAS 模块来替代 J2EE 安全性,而不是使用内置的遵循规范的应用程序服务器机制来进行验证和授权。要注意不要脱离 J2EE 规范提供的验证机制,如果脱离了此规范,这将是系统存在安全漏洞以及厂商兼容性问题的主要原因。类似地,要使用 servlet 和 EJB 规范提供的授权机制,并且如果您要偏离这些规范的话,要确保使用规范定义的 API(例如 getCallerPrincipal())作为实现的基础。通过这种方式,您将能够利用厂商提供的强安全性基础设施,其中,业务要求需要支持复杂的授权规则。
其他常见的问题包括使用不遵循 J2EE 规范的持久性机制(这使得事务管理变得困难)、在J2EE程序中使用不适当的J2SE 方法(例如线程或 singleton),以及使用您自己的方法解决程序到程序(program-to-program)的通信,而不是使用 J2EE 内在支持的机制(例如 JCA、JMS 或 Web 服务)。当您将一个遵循 J2EE 的服务器移植到其他的服务器上,或者移植到相同服务器的新版本上,上述的设计选择将会造成无数的问题。唯一要背离规范的情况是,当一个问题在规范的范围内无法解决的时候。例如,安排执行定时的业务逻辑在 EJB2.1 出现之前是一个问题,在类似这样的情况下,我们建议当有厂商提供的解决方案时就使用厂商提供的解决方案(例如 WebSphere Application Server Enterprise 中的 Scheduler 工具),而在没有厂商提供的解决方案时就使用第三方提供的工具。如果使用厂商提供的解决方案,应用程序的维护以及将其移植到新的规范版本将是厂商的问题,而不是您的问题。
最后,要注意不要太早地采用新技术。太过于热衷采用还没有集成到 J2EE 规范的其他部分或者还没有集成到厂商的产品中的技术常会带来灾难性的后果。支持是关键的——如果您的厂商不直接支持一种特定的在 JSR 中提出的技术,但此技术还没有被 J2EE 所接受,那么您就不应该采用此技术。毕竟,我们中的大多数人从事解决业务问题,而不是推进技术的发展。
4. 从一开始就计划使用 J2EE 安全性。
启用 WebSphere 安全性。这使您的 EJB 和 URL 至少可以让所有授权用户访问。不要问为什么——照着做就是了。
在与我们合作的客户中,一开始就打算启用 WebSphere J2EE 安全性的顾客是非常少的,这一点一直让我们感到吃惊。据我们估计大约只有 50% 的顾客一开始就打算使用此特性。例如,我们曾与一些大型的金融机构(银行、代理等等)合作过,他们也没有打算启用安全性。幸运的是,这种问题在部署之前的检查时就得以解决.
不使用 J2EE 安全性是危险的事情。假设您的应用程序需要安全性(几乎所有的应用程序都需要),您敢打赌您的开发人员能够构建出自己的安全性系统,而这个系统比您从 J2EE 厂商那里买来的更好。这可不是个好的赌注,为分布式的应用程序提供安全性是异常困难的。例如,您需要使用网络安全加密令牌控制对 EJB 的访问。以我们的经验看来,大多数自己构建的安全性系统是不安全的,并且有重大的缺陷,这使产品系统极其脆弱。
一些不使用 J2EE 安全性的理由包括:担心性能的下降,相信其他的安全性(例如 Netegrity SiteMinder)可以取代 J2EE 安全性,或者是不知道 WebSphere Application Server 安全特性及功能。不要陷入这些陷阱之中,尤其是,尽管像 Netegrity SiteMinder 这样的产品能够提供优秀的安全特性,但是仅仅其自身不可能保护整个 J2EE 应用程序。这些产品必须与 J2EE 应用程序服务器联合起来才可能全面地保护您的系统。
其他的一种常见的不使用 J2EE 安全性的原因是:基于角色的模型没有提供足够的粒度访问控制以满足复杂的业务规则。尽管事实是这样的,但这也不应该成为不使用 J2EE 安全性的理由。相反地,应该将 J2EE 验证及 J2EE 角色与特定的扩展规则结合起来。如果复杂的业务规则需要做出安全性决策,那就编写相应的代码,其安全性决策要基于可以直接使用的以及可靠的 J2EE 验证信息(用户 ID 和角色)。
5. 创建您所知道的。
反复的开发工作将使您能够逐渐地掌握所有的 J2EE 模块。要从创建小而简单的模块开始而不是从一开始就马上涉及到所有的模块。
我们必须承认 J2EE 是庞大的体系。如果一个开发小组只是开始使用 J2EE,这将很难一下子就能掌握它。在 J2EE 中有太多的概念和 API 需要掌握。在这种情况下,成功掌握 J2EE 的关键是从简单的步骤开始做起。
这种方法可以通过在您的应用程序中创建小而简单的模块来得到最好的实现。如果一个开发小组通过创建一个简单的域模型以及后端的持久性机制(也许使用的是 JDBC),并且对其进行了完整的测试,这会增强他们的自信心,于是他们会使用该域模型去掌握使用 servlet 和 JSP 的前端开发。如果一个开发组发现有必要使用 EJB,他们也会类似地开始在容器管理的持久性 EJB 组件之上使用简单的会话 Facades,或者使用基于 JDBC 的数据访问对象(JDBC-based Data Access Objects,DAO),而不是跳过这些去使用更加复杂的构造(例如消息驱动bean和JMS)。
这种方法并不是什么新方法,但是很少有开发组以这种方式来培养他们的技能。相反地,多数开发组由于尝试马上就构建所有的模块,同时涉及 MVC 中的视图层、模型层和控制器层,这样做的结果是他们往往会陷入进度的压力之中。他们应该考虑一些敏捷(Agile)开发方法,例如极限编程(XP),这种开发方法采用一种增量学习及开发方法。在 XP 中有一种称为 ModelFirst 的过程,这个过程涉及到首先构建域模型作为一种机制来组织和实现用户场景。基本说来,您要构建域模型作为您要实现的用户场景的首要部分,然后在域模型之上构建一个用户界面(UI)作为用户场景实现的结果。这种方法非常适合让一个开发组一次只学到一种技术,而不是让他们同时面对很多种情况(或者让他们读很多书),这会令他们崩溃的。
还有,对每个应用程序层重复的开发可能会包含一些适当的模式及最佳实践。如果您从应用程序的底层开始应用一些模式如数据访问对象和会话 Facades,您就不应该在您的JSP和其他视图对象中使用域逻辑。
最后,当您开发一些简单的模块时,在开始的初期就可以对您的应用程序进行性能测试。如果直到应用程序开发的后期才进行性能测试的话,这往往会出现灾难性的后果。
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 和其他管理界面的情况下。
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 开发成为一种可管理的过程。尽管我们并没有给出所有在实践中使用这些模式的必要细节,但是我们希望能够给您足够的指点和指导,以帮助您决定下一步要做什么。
梦幻人生,仿如流星,触不可及,一眼而去!
首先,本文的目标读者是正在从事技术工作的架构师。为了避免浪费大家的才智,我会避免讲述一些陈腐的最佳实践,例如“日常构建(build daily)”、“测试一切(test everything)”和“经常集成( integrate often)。 任何具有称职架构师的项目都有分工明确的、定义良好的团队结构。他们还为进行编码检查、构建代码(每日或在需要时)、进行测试(单元、集成和系统的)、部署和配置/释放管理而具备已记录的过程。
其次,我将跳过通常吹捧的最佳实践,例如“基于接口的设计”、“使用著名的设计模型”以及“使用面向服务的架构”等。相反,我将集中讲述我曾学过并且使用了若干年的6(不是很多)个方面的in-the-trench课程。最后,本文的目的是让您思考一下自己的架构,提供工作代码示例或者解决方案超出了本文的范围。下面就让我介绍一下这6课:
1、切勿绕过服务器端验证
2、安全并非是附加物
3、国际化(I18N)不再是纸上谈兵
4、在MVC表示中避免共同的错误
5、不要被POJO束缚住手脚
6、数据访问并不能托管O/R映射
第1课:切勿绕过服务器端验证
作为一位软件顾问,我曾有机会不但设计并实现了Web应用程序,而且还评估/审核了许多Web应用程序。在复杂的、并且用JavaScript客户端封装的应用程序内,我经常遇到对用户输入信息执行大量检查的Web页面。即使HTML元素具有数据有效性的属性也如此,例如MAXLENGTH。只有在成功验证所有输入信息后,才能提交HTML表单。结果,一旦服务器端收到通知表单(请求),便恰当地执行业务逻辑。
在此,您发现问题了么?开发人员已经做了许多重要的假设。例如,他们假设所有的Web应用程序用户都同样诚实。开发人员还假设所有用户将总是使用他们测试过的浏览器访问Web应用程序。还有很多其他的假设。这些开发人员忘记了利用可以免费得到的工具,通过命令行很容易地模拟类似浏览器的行为。事实上,通过在浏览器窗口中键入适当的URL,您可以发送任何“posted”表单,尽管如此,通过禁用这些页面的GET请求,您很容易地阻止这样的“表单发送”。但是,您不能阻止人们模拟甚至创建他们自己的浏览器来入侵您的系统。
根本的问题在于开发人员不能确定客户端验证与服务器端验证的主要差别。两者的主要差别不在于验证究竟发生在哪里,例如在客户端或者在服务器端。主要的差别在于验证背后的目的不同。
客户端验证仅仅是方便。执行它可为用户提供快速反馈??使应用程序似乎做出响应,给人一种运行桌面应用程序的错觉。
另一方面,服务器端验证是构建安全Web应用程序必需的。不管在客户端一侧输入的是什么,它可以确保客户端送往服务器的所有数据都是有效的。
因而,只有服务器端验证才可以提供真正应用程序级的安全。许多开发人员陷入了错误感觉的圈套:只有在客户端进行所有数据的验证才能确保安全。下面是说明此观点的一个常见的示例:
一个典型的登录页面拥有一个用来输入用户名的文本框和一个输入密码的文本框。在服务器端,某人在接收servlet中可能遇到一些代码,这些代码构成了下面形式的SQL查询:
"SELECT * FROM SecurityTable WHERE username = @#" + form.getParameter("username") + "@# AND password = @#" + form.getParameter("password") + "@#;",并执行这些代码。如果查询在结果集的某一行返回,则用户登录成功,否则用户登录失败。
第一个问题是构造SQL的方式,但现在让我们暂时忽略它。如果用户在用户名中输入“Alice@#--”会怎样呢?假设名为“Alice”的用户已经在SecurityTable中,这时此用户(更恰当的说法是黑客)成功地登录。我将把找出为什么会出现这种情况的原因做为留给您的一道习题。
许多创造性的客户端验证可以阻止一般的用户从浏览器中这样登录。但对于已经禁用了JavaScript的客户端,或者那些能够使用其他类似浏览器程序直接发送命令(HTTP POST和GET命令)的高级用户(或者说黑客)来说,我们又有什么办法呢?服务器端验证是防止这种漏洞类型所必须的。这时,SSL、防火墙等都派不上用场了。
第2课:安全并非是附加物
如第1课所述,我曾有幸研究过许多Web应用程序。我发现所有的JavaServer Page(JSP)都有一个共同的主题,那就是具有类似下面伪代码的布局:
<%
User user =
session.getAttribute("User");
if(user == null)
{
// redirect to
// the logon page…
}
if(!user.role.equals("manager"))
{
// redirect to the
// "unauthorized" page…
}
%>
<!-
HTML, JavaScript, and JSP
code to display data and
allow user interaction -->
如果项目使用诸如Struts这样的MVC框架,所有的Action Bean都会具有类似的代码。尽管最后这些代码可能运行得很好,但如果您发现一个bug,或者您必须添加一个新的角色(例如,“guest”或者“admin”),这就会代表一场维护恶梦。
此外,所有的开发人员,不管您多年轻,都需要熟悉这种编码模式。当然,您可以用一些JSP标签来整理JSP代码,可以创建一个清除派生Action Bean的基本Action Bean。尽管如此,由于与安全相关的代码会分布到多个地方,所以维护时的恶梦仍旧存在。由于Web应用程序的安全是强迫建立在应用程序代码的级别上(由多个开发人员),而不是建立在架构级别上,所以Web应用程序还是很可能存在弱点。
很可能,根本的问题是在项目接近完成时才处理安全性问题。最近作为一名架构师,我曾在一年多的时间里亲历了某一要实现项目的6个版本,而直到第四版时我们才提到了安全性??即使该项目会将高度敏感的个人数据暴露于Web上,我们也没有注意到安全性。为了更改发布计划,我们卷入了与项目资助人及其管理人员的争斗中,以便在第一版中包含所有与安全相关的功能,并将一些“业务”功能放在后续的版本中。最终,我们赢得了胜利。而且由于应用程序的安全性相当高,能够保护客户的私有数据,这一点我们引以为荣,我们的客户也非常高兴。
遗憾的是,在大多数应用程序中,安全性看起来并未增加任何实际的商业价值,所以直到最后才解决。发生这种情况时,人们才匆忙开发与安全相关的代码,而丝毫没有考虑解决方案的长期可维护性或者健壮性。忽视该安全性的另一个征兆是缺乏全面的服务器端验证,如我在第1课中所述,这一点是安全Web应用程序的一个重要组成部分。
记住:J2EE Web应用程序的安全性并非仅仅是在Web.xml 和ejb-jar.xml文件中使用合适的声明,也不是使用J2EE技术,如Java 认证和授权服务(Java Authentication and Authorization Service,JAAS)。而是经过深思熟虑后的设计,且实现一个支持它的架构。
第3课:国际化(I18N)不再是纸上谈兵
当今世界的事实是许多英语非母语的人们将访问您的公共Web应用程序。随着电子政务的实行,由于它允许人们(某个国家的居民)在线与政府机构交互,所以这一点特别真实。这样的例子包括换发驾照或者车辆登记证。许多第一语言不是英语的人们很可能将访问这样的应用程序。国际化(即:“i18n”,因为在“internationalization”这个单词中,字母i和字母n之间一共有18个字母)使得您的应用程序能够支持多种语言。
显然,如果您的JSP 页面中有硬编码的文本,或者您的Java代码返回硬编码的错误消息,那么您要花费很多时间开发此Web应用程序的西班牙语版本。然而,在Web应用程序中,为了支持多种语言,文本不是惟一必须“具体化”的部分。因为许多图像中嵌有文字,所以图形和图像也应该是可配置的。在极端的情况下,图像(或者颜色)在不同的文化背景中可能有完全不同的意思。类似地,任何格式化数字和日期的Java代码也必须本地化。但问题是:您的页面布局可能也需要更改。
例如,如果您使用HTML表格来格式化和显示菜单选项、应用程序题头或注脚,则您可能必须为每一种支持的语言更改每一栏的最小宽度和表格其他可能的方面。为了适应不同的字体和颜色,您可能必须为每一种语言使用单独的样式表。
显然,现在创建一个国际化的Web应用程序面临的是架构挑战而不是应用程序方面的挑战。一个架构良好的Web应用程序意味着您的JSP页面和所有与业务相关的(应用程序特有的)Java代码都不知不觉地选择了本地化。要记住的教训是:不要因为Java、J2EE支持国际化而不考虑国际化。您必须从第一天起就记住设计具有国际化的解决方案。
第4课:在MVC表示中避免共同的错误
J2EE开发已经足够成熟,在表示层,大多数项目使用MVC架构的某些形式,例如Struts。在这样的项目中,我常见到的现象是对MVC模式的误用。下面是几个示例。
常见的误用是在模型层(例如,在Struts的Action Bean中)实现了所有的业务逻辑。不要忘了,表示层的模型层仍然是表示层的一部分。使用该模型层的正确方法是调用适当的业务层服务(或对象)并将结果发送到视图层(view layer)。用设计模式术语来说,MVC表示层的模型应该作为业务层的外观(Fa?ade)来实现。更好的方法是,使用核心J2EE模式(Core J2EE Patterns)中论述到的Business Delegate模式。这段自书中摘录的内容精彩地概述了将您的模型作为Business Delegate来实现的要点和优点:
Business Delegate起到客户端业务抽象化的作用。它抽象化,进而隐藏业务服务的实现。使用Business Delegate,可以降低表示层客户端和系统的业务服务.之间的耦合程度。根据实现策略不同,Business Delegate可以在业务服务API的实现中,保护客户端不受可能的变动性影响。这样,在业务服务API或其底层实现变化时,可以潜在地减少必须修改表示层客户端代码的次数。
另一个常见的错误是在模型层中放置许多表示类型的逻辑。例如,如果JSP页面需要以指定方式格式化的日期或者以指定方式排序的数据,某些人可能将该逻辑放置在模型层,对该逻辑来说,这是错误的地方。实际上,它应该在JSP页面使用的一组helper类中。当业务层返回数据时,Action Bean应该将数据转发给视图层。这样,无需创建模型和视图之间多余的耦合,就能够灵活支持多个视图层(JSP、Velocity、XML等)。也使视图能够确定向用户显示数据的最佳方式。
最后,我见过的大多数MVC应用程序都有未充分应用的控制器。例如,绝大多数的Struts应用程序将创建一个基本的Action类,并完成所有与安全相关的功能。其他所有的Action Bean都是此基类的派生类。这种功能应该是控制器的一部分,因为如果没有满足安全条件,则首先调用不应该到达Action Bean(即:模型)。记住,一个设计良好的MVC架构的最强大功能之一是存在一个健壮的、可扩展的控制器。您应该利用该能力以加强自己的优势。
第5课:不要被POJO束缚住手脚
我曾目睹许多项目为了使用Enterprise JavaBean而使用Enterprise JavaBean。因为EJB似乎给项目带来优越感和妄自尊大的表现,所以有时它是显酷的要素(coolness factor)。而其他时候,它会使J2EE和EJB引起混淆。记住,J2EE和EJB不是同意词。EJB只是J2EE 的一部分,J2EE 是包含JSP、servlet、Java 消息服务(JMS)、Java数据库连接(JDBC)、JAAS、 Java管理扩展(JMX)和EJB在内的一系列技术,同样也是有关如何共同使用这些技术建立解决方案的一组指导原则和模式。
如果在不需要使用EJB的情况下使用EJB,它们可能会影响程序的性能。与老的Web服务器相比,EJB一般对应用服务器有更多的需求。EJB提供的所有增值服务一般需要消耗更大的内存和更多的CPU时间。许多应用程序不需要这些服务,因此应用服务器要与应用程序争夺资源。
在某些情况下,不必要地使用EJB可能使应用程序崩溃。例如,最近我遇到了一个在开源应用服务器上开发的应用程序。业务逻辑封装在一系列有状态会话bean(EJB)中。开发人员为了在应用服务器中完全禁用这些bean的“钝化”费了很大的劲。客户端要求应用程序部署在某一商用应用服务器上,而该服务器是客户端技术栈的一部分。该应用服务器却不允许关闭“钝化”功能。事实上,客户端不想改变与其合作的应用服务器的设任何置。结果,开发商碰到了很大的麻烦。(似乎)有趣的事情是开发商自己都不能给出为什么将代码用EJB(而且还是有状态会话bean)实现的好理由。不仅仅是开发商会遇到性能问题,他们的程序在客户那里也无法工作。
在Web应用程序中,无格式普通Java 对象(POJO)是EJB强有力的竞争者。POJO是轻量级的,不像EJB那样负担额外的负担。在我看来,对许多EJB的优点,例如对象入池,估计过高。POJO是您的朋友,不要被它束缚住手脚。
第6课:数据访问并不能托管O/R映射
我曾参与过的所有Web应用程序都向用户提供从其他地方存取的数据,并且因此需要一个数据访问层。这并不是说所有的项目都需要标识并建立这样一个层,这仅仅说明这样层的存在不是隐含的就是明确的。如果是隐含的数据层,数据层是业务对象(即:业务服务)层的一部分。这适用于小型应用程序,但通常与大一些项目所接受的架构指导原则相抵触。
总之,数据访问层必须满足或超出以下四个标准:
具有透明性
业务对象在不知道数据源实现的具体细节情况下,可以使用数据源。由于实现细节隐藏在数据访问层的内部,所以访问是透明的。
易于迁移
数据访问层使应用程序很容易迁移到其他数据库实现。业务对象不了解底层的数据实现,所以迁移仅仅涉及到修改数据访问层。进一步地说,如果您正在部署某种工厂策略,您可以为每个底层的存储实现提供具体的工厂实现。如果是那样的话,迁移到不同的存储实现意味着为应用程序提供一个新的工厂实现。
尽量减少业务对象中代码复杂性
因为数据访问层管理着所有的数据访问复杂性,所以它可以简化业务对象和使用数据访问层的其他数据客户端的代码。数据访问层,而不是业务对象,含有许多与实现相关的代码(例如SQL语句)。这样给开发人员带来了更高的效率、更好的可维护性、提高了代码的可读性等一系列好处。
把所有的数据访问集中在单独的层上
由于所有的数据访问操作现在都委托给数据访问层,所以您可以将这个单独的数据访问层看做能够将应用程序的其他部分与数据访问实现相互隔离的层。这种集中化可以使应用程序易于维护和管理。
注意:这些标准都不能明确地调出对O/R(对象到关系)映射层的需求。O/R映射层一般用O/R映射工具创建,它提供对象对关系数据结构的查看和感知(look-and-feel)。在我看来,在项目中使用O/R映射与使用EJB类似。在大多数情况下,并不要求它。对于包含中等规模的联合以及多对多关系的关系型数据库来说,O/R映射会变得相当复杂。由于增加O/R 映射解决方案本身的内在复杂性,例如延迟加载(lazy loading)、高速缓冲等,您将为您的项目带来更大的复杂性(和风险)。
为了进一步支持我的观点,我将指出按照Sun Microsystem所普及的实体Bean(O/R映射的一种实现)的许多失败的尝试,这是自1.0版以来一直折磨人的难题。在SUN的防卫措施中,一些早期的问题是有关EJB规范的开发商实现的。这依次证明了实体Bean规范自身的复杂性。结果,大多数J2EE架构师一般认为从实体Bean中脱离出来是一个好主意。
大多数应用程序在处理他们的数据时,只能进行有限次数的查询。在这样的应用程序中,访问数据的一种有效方法是实现一个数据访问层,该层实现执行这些查询的一系列服务(或对象、或API)。如上所述,在这种情况下,不需要O/R映射。当您要求查询灵活性时,O/R映射正合适,但要记住:这种附加的灵活性并不是没有代价的。
就像我承诺的那样,在本文中,我尽量避免陈腐的最佳实践。相反,关于J2EE项目中每一位架构师必须做出的最重要的决定,我集中讲解了我的观点。最后,您应该记住:J2EE并非某种具体的技术,也不是强行加入到解决方案中的一些首字母缩写。相反,您应该在适当的时机,恰当的地方,使用合适的技术,并遵循J2EE的指导原则和J2EE中所包含的比技术本身重要得多的实践。
JAVA 12个最重要的J2EE最佳实践
1、始终使用 MVC 框架。
2、在每一层都应用自动单元测试和测试管理。
3、按照规范来进行开发,而不是按照应用服务器来进行开发。
4、从一开始就计划使用 J2EE 安全性。
5、创建您所知道的。
6、当使用 EJB 组件时,始终使用会话 Facades。
7、使用无状态会话 bean,而不是有状态会话 bean.
8、使用容器管理的事务。
9、将 JSP 作为表示层的首选。
10、当使用 HttpSession 时,尽量只将当前事务所需要的状态保存其中,其他内容不要保存在 HttpSession 中。
11、在 WebSphere 中,启动动态缓存,并使用 WebSphere servlet 缓存机制。
12、为了提高程序员的工作效率,将 CMP 实体 bean 作为 O/R 映射的首选解决方案。
1. 始终使用 MVC 框架。
MVC 框架可以将业务逻辑(Java beans 和 EJB 组件)、控制器逻辑(Servlets/Struts 动作)、表示层(JSP、XML/XSLT)清晰地分离开来。良好的分层可以带来许多好处。
MVC 框架对于成功使用 J2EE 是如此重要,以致没有其他最佳实践可以与其相提并论。模型-视图-控制器(MVC)是设计 J2EE 应用程序的基础。MVC 将您的程序代码简单地划分下面几个部分:
负责业务逻辑的代码(即模型——通常使用 EJB 或者普通的 Java 对象来实现)。
负责用户界面显示的代码(即视图——通常通过 JSP 及标记库来实现,有时也使用 XML 和 XSLT 来实现)。
负责应用程序流程的代码(即控制器——通常使用 Java Servlet 或像 Struts 控制器这样的类来实现)。
如果您不遵循基本的 MVC 框架,在开发过程中就会出现许多的问题。最常见的问题就是在视图部分添加了太多的成分,例如,可能存在使用 JSP 标记来执行数据库访问,或者在 JSP 中进行应用程序的流程控制,这在小规模的应用程序中是比较常见的,但是,随着后期的开发,这样做将会带来问题,因为 JSP 逐步变得越来越难以维护和调试。
类似地,我们也经常看到将视图层构建到业务逻辑的情况。例如,一个常见的问题就是将在构建视图时使用的 XML 解析技术直接应用到业务层。业务层应该对业务对象——而不是绑定到视图的特定数据表示进行操作。
然而,只是具有合适的组件并不一定意味着可以使您的应用程序得到合适的分层。我们常常见到一些应用程序包含 servlet、JSP 和 EJB 组件所有这三项,然而,其主要的业务逻辑却是在 servlet 层实现的,或者应用程序导航是在 JSP 中处理的。您必须进行严格的代码检查并重构您的代码,以确保应用程序的业务逻辑只在模型层(Model layer)进行处理,应用程序导航只通过控制器层(Controller layer)进行处理,而您的视图(Views)只是将传递过来的模型对象以 HTML 及 JavaScript 的形式表示出来。
2. 在应用程序的每一层都使用自动单元测试和测试管理。
不要只是测试您的图形用户界面(GUI)。分层的测试使测试及维护工作变得极其简单。
在过去的几年中,在方法学领域有了相当大的革新,例如新出现的被称为 Agile(例如 SCRUM [Schwaber] 和极限编程 [Beck1])的轻量级方法现在已经得到了很普遍的应用。几乎所有的这些方法中的一个共同的特征是它们都提倡使用自动的测试工具,这些工具可以帮助开发人员用更少的时间进行回归测试 (regression testing),并可以帮助他们避免由于不充分的回归测试造成的错误,因此可以用来提高程序员的工作效率。实际上,还有一种被称为 Test-First Development [Beck2] 的方法,这种方法甚至提倡在开发实际的代码之前就先编写单元测试。然而,在您测试代码之前,您需要将代码分割成一些可测试的片断。一个“大泥球”是难以测试的,因为它不是只实现一个简单的易于识别的功能。如果您的每个代码片断实现多个方面的功能,这样的代码将难以保证其完全的正确性。
MVC 框架(以及 J2EE 中的 MVC 实现)的一个优点就是元素的组件化能够(实际上,相当的简单)对您的应用程序进行单元测试。因此,您可以方便地对实体 bean、会话 bean 以及 JSP 独立编写测试用例,而不必考虑其他的代码。现在有许多用于 J2EE 测试的框架和工具,这些框架及工具使得这一过程更加简单。例如,JUnit(是一种由 junit.org 开发的开放源代码工具)和 Cactus(由 Apache 开发的开放源代码工具)对于测试 J2EE 组件都非常有用。[Hightower] 详细探讨了如何在 J2EE 中使用这些工具。
尽管所有这些详述了怎样彻底地测试您的应用程序,但是我们仍然看到一些人认为只要他们测试了 GUI(可能是基于 Web 的 GUI,或者是独立的 Java 应用程序),则他们就全面地测试了整个应用程序。GUI 测试很难达到全面的测试,有以下几种原因。首先,使用 GUI 测试很难彻底地测试到系统的每一条路径,GUI 仅仅是影响系统的一种方式,可能存在后台运算、脚本和各种各样的其他访问点,这也需要进行测试。然而,它们通常并不具有 GUI。第二,GUI 级的测试是一种非常粗粒度的测试。这种测试只是在宏观水平上测试系统的行为。这意味着一旦发现存在问题,则与此问题相关的整个子系统都要进行检查,这使得找出 bug(缺陷)将是非常困难的事情。第三,GUI 测试通常只有在整个开发周期的后期才能很好地得到测试,这是因为只有这那个时候 GUI 才得到完整的定义。这意味着只有在后期才可能发现潜在的 bug。第四,一般的开发人员可能没有自动的 GUI 测试工具。因此,当一个开发人员对代码进行更改时,没有一种简单的方法来重新测试受到影响的子系统。这实际上不利于进行良好的测试。如果开发人员具有自动的代码级单元测试工具,开发人员就能够很容易地运行这些工具以确保所做的更改不会破坏已经存在的功能。最后,如果添加了自动构建功能,则在自动构建过程中添加一个自动的单元测试工具是非常容易的事情。当完成这些设置以后,整个系统就可以有规律地进行重建,并且回归测试几乎不需要人的参与。
另外,我们必须强调,使用 EJB 和 Web 服务进行分布式的、基于组件的开发使得测试单个的组件变得非常必要。如果没有“GUI”需要测试,您就必须进行低级(lower-level)测试。最好以这种方式开始测试,省得当您将分布式的组件或 Web 服务作为您的应用程序的一部分时,您不得不花费心思重新进行测试。
总之,通过使用自动的单元测试,能够很快地发现系统的缺陷,并且也易于发现这些缺陷,使得测试工作变得更加系统化,因此整体的质量也得以提高。
3. 按照规范来进行开发,而不是按照应用服务器来进行开发。
要将规范熟记于心,如果要背离规范,要经过慎密的考虑后才可以这样做。这是因为当您背离规则的时候,您所做的事情往往并不是您应该做的事情。
当您要背离 J2EE 可以允许您做的事情的时候,这很容易让使您遭受不幸。我们发现有一些开发人员钻研一些 J2EE 允许之外的东西,他们认为这样做可以“稍微”改善J2EE的性能,而他们最终只会发现这样做会引起严重的性能问题,或者在以后的移植(从一个厂商到另一个厂商,或者是更常见的从一个版本到另一个版本)中会出现问题。实际上,这种移植问题是如此严重,以致 [Beaton] 将此原则称为移植工作的基本最佳实践。
现在有好几个地方如果不直接使用 J2EE 提供的方法肯定会产生问题。一个常见的例子就是开发人员通过使用 JAAS 模块来替代 J2EE 安全性,而不是使用内置的遵循规范的应用程序服务器机制来进行验证和授权。要注意不要脱离 J2EE 规范提供的验证机制,如果脱离了此规范,这将是系统存在安全漏洞以及厂商兼容性问题的主要原因。类似地,要使用 servlet 和 EJB 规范提供的授权机制,并且如果您要偏离这些规范的话,要确保使用规范定义的 API(例如 getCallerPrincipal())作为实现的基础。通过这种方式,您将能够利用厂商提供的强安全性基础设施,其中,业务要求需要支持复杂的授权规则。
其他常见的问题包括使用不遵循 J2EE 规范的持久性机制(这使得事务管理变得困难)、在J2EE程序中使用不适当的J2SE 方法(例如线程或 singleton),以及使用您自己的方法解决程序到程序(program-to-program)的通信,而不是使用 J2EE 内在支持的机制(例如 JCA、JMS 或 Web 服务)。当您将一个遵循 J2EE 的服务器移植到其他的服务器上,或者移植到相同服务器的新版本上,上述的设计选择将会造成无数的问题。唯一要背离规范的情况是,当一个问题在规范的范围内无法解决的时候。例如,安排执行定时的业务逻辑在 EJB2.1 出现之前是一个问题,在类似这样的情况下,我们建议当有厂商提供的解决方案时就使用厂商提供的解决方案(例如 WebSphere Application Server Enterprise 中的 Scheduler 工具),而在没有厂商提供的解决方案时就使用第三方提供的工具。如果使用厂商提供的解决方案,应用程序的维护以及将其移植到新的规范版本将是厂商的问题,而不是您的问题。
最后,要注意不要太早地采用新技术。太过于热衷采用还没有集成到 J2EE 规范的其他部分或者还没有集成到厂商的产品中的技术常会带来灾难性的后果。支持是关键的——如果您的厂商不直接支持一种特定的在 JSR 中提出的技术,但此技术还没有被 J2EE 所接受,那么您就不应该采用此技术。毕竟,我们中的大多数人从事解决业务问题,而不是推进技术的发展。
4. 从一开始就计划使用 J2EE 安全性。
启用 WebSphere 安全性。这使您的 EJB 和 URL 至少可以让所有授权用户访问。不要问为什么——照着做就是了。
在与我们合作的客户中,一开始就打算启用 WebSphere J2EE 安全性的顾客是非常少的,这一点一直让我们感到吃惊。据我们估计大约只有 50% 的顾客一开始就打算使用此特性。例如,我们曾与一些大型的金融机构(银行、代理等等)合作过,他们也没有打算启用安全性。幸运的是,这种问题在部署之前的检查时就得以解决.
不使用 J2EE 安全性是危险的事情。假设您的应用程序需要安全性(几乎所有的应用程序都需要),您敢打赌您的开发人员能够构建出自己的安全性系统,而这个系统比您从 J2EE 厂商那里买来的更好。这可不是个好的赌注,为分布式的应用程序提供安全性是异常困难的。例如,您需要使用网络安全加密令牌控制对 EJB 的访问。以我们的经验看来,大多数自己构建的安全性系统是不安全的,并且有重大的缺陷,这使产品系统极其脆弱。
一些不使用 J2EE 安全性的理由包括:担心性能的下降,相信其他的安全性(例如 Netegrity SiteMinder)可以取代 J2EE 安全性,或者是不知道 WebSphere Application Server 安全特性及功能。不要陷入这些陷阱之中,尤其是,尽管像 Netegrity SiteMinder 这样的产品能够提供优秀的安全特性,但是仅仅其自身不可能保护整个 J2EE 应用程序。这些产品必须与 J2EE 应用程序服务器联合起来才可能全面地保护您的系统。
其他的一种常见的不使用 J2EE 安全性的原因是:基于角色的模型没有提供足够的粒度访问控制以满足复杂的业务规则。尽管事实是这样的,但这也不应该成为不使用 J2EE 安全性的理由。相反地,应该将 J2EE 验证及 J2EE 角色与特定的扩展规则结合起来。如果复杂的业务规则需要做出安全性决策,那就编写相应的代码,其安全性决策要基于可以直接使用的以及可靠的 J2EE 验证信息(用户 ID 和角色)。
5. 创建您所知道的。
反复的开发工作将使您能够逐渐地掌握所有的 J2EE 模块。要从创建小而简单的模块开始而不是从一开始就马上涉及到所有的模块。
我们必须承认 J2EE 是庞大的体系。如果一个开发小组只是开始使用 J2EE,这将很难一下子就能掌握它。在 J2EE 中有太多的概念和 API 需要掌握。在这种情况下,成功掌握 J2EE 的关键是从简单的步骤开始做起。
这种方法可以通过在您的应用程序中创建小而简单的模块来得到最好的实现。如果一个开发小组通过创建一个简单的域模型以及后端的持久性机制(也许使用的是 JDBC),并且对其进行了完整的测试,这会增强他们的自信心,于是他们会使用该域模型去掌握使用 servlet 和 JSP 的前端开发。如果一个开发组发现有必要使用 EJB,他们也会类似地开始在容器管理的持久性 EJB 组件之上使用简单的会话 Facades,或者使用基于 JDBC 的数据访问对象(JDBC-based Data Access Objects,DAO),而不是跳过这些去使用更加复杂的构造(例如消息驱动bean和JMS)。
这种方法并不是什么新方法,但是很少有开发组以这种方式来培养他们的技能。相反地,多数开发组由于尝试马上就构建所有的模块,同时涉及 MVC 中的视图层、模型层和控制器层,这样做的结果是他们往往会陷入进度的压力之中。他们应该考虑一些敏捷(Agile)开发方法,例如极限编程(XP),这种开发方法采用一种增量学习及开发方法。在 XP 中有一种称为 ModelFirst 的过程,这个过程涉及到首先构建域模型作为一种机制来组织和实现用户场景。基本说来,您要构建域模型作为您要实现的用户场景的首要部分,然后在域模型之上构建一个用户界面(UI)作为用户场景实现的结果。这种方法非常适合让一个开发组一次只学到一种技术,而不是让他们同时面对很多种情况(或者让他们读很多书),这会令他们崩溃的。
还有,对每个应用程序层重复的开发可能会包含一些适当的模式及最佳实践。如果您从应用程序的底层开始应用一些模式如数据访问对象和会话 Facades,您就不应该在您的JSP和其他视图对象中使用域逻辑。
最后,当您开发一些简单的模块时,在开始的初期就可以对您的应用程序进行性能测试。如果直到应用程序开发的后期才进行性能测试的话,这往往会出现灾难性的后果。
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 和其他管理界面的情况下。
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 开发成为一种可管理的过程。尽管我们并没有给出所有在实践中使用这些模式的必要细节,但是我们希望能够给您足够的指点和指导,以帮助您决定下一步要做什么。
梦幻人生,仿如流星,触不可及,一眼而去!
相关推荐
书中的案例分析和最佳实践将有助于你提升解决问题的能力,并在解决复杂系统设计挑战时更有信心。同时,阅读DVT.Press.The.J2EE.eBook-LiB这样的电子书资源,将加深你对J2EE架构的理解,助力你在IT行业中取得更大的...
它全面地涵盖了成为J2EE架构师所需掌握的各项核心技术,旨在帮助读者深入理解J2EE平台的核心概念、设计原则和最佳实践。 J2EE(Java 2 Platform, Enterprise Edition)是Java平台的企业版,主要用于构建分布式、...
《J2EE架构师手册》这本书可能详细阐述了这些技术的原理、实践方法以及最佳实践,对于想要成为或已经是J2EE架构师的人来说,是一份不可多得的参考资料。通过阅读和学习这本书,读者可以提升对J2EE体系的理解,更好地...
这本《J2EE架构师手册》很可能详细讲解了这些技术的原理、最佳实践、设计模式和案例研究,帮助读者从整体上理解J2EE架构的构建和优化。通过阅读HTML文件,你将能够深入了解如何利用J2EE组件构建可扩展、健壮的分布式...
总的来说,《J2EE架构师认证指南》将带领读者全面了解J2EE平台的各项技术,并通过实际案例和最佳实践,培养出能够设计和实施大型企业级应用的高级架构师。结合压缩包中的“hibernate_reference.pdf”,读者可以...
蓝图提供了分布式组件架构的最佳实践和设计指导,为开发人员提供了一套标准和准则。 Rational统一过程(RUP)是一种软件开发方法学,它强调了严格的任务分配和职责界定,旨在确保项目按时按预算交付高质量的软件。...
这本书深入浅出地探讨了J2EE平台的核心技术和最佳实践,旨在帮助读者从宏观和微观两个层面理解并设计出高效、可扩展且易于维护的企业级应用。 J2EE(Java 2 Platform, Enterprise Edition)是Oracle公司推出的用于...
首先,J2EE的最佳实践之一是分层架构。这种架构模式将应用分为表示层、业务逻辑层和数据访问层,有助于分离关注点,提高代码复用性。例如,使用Servlet和JSP进行用户界面处理,EJB(Enterprise JavaBeans)处理业务...
J2EE(Java 2 Platform, Enterprise Edition)是Oracle公司推出的用于构建企业级...这要求你具备深厚的Java基础,对J2EE规范有深入理解,同时还要熟悉软件工程的最佳实践,如设计模式、测试驱动开发以及持续集成等。
本篇文章将详细阐述两个核心的最佳实践:一是始终使用MVC(Model-View-Controller)架构模式,二是避免将业务逻辑放入控制层。 1. 始终使用MVC模式: MVC模式是构建Java EE应用的基础,它有效地将业务逻辑(Model)...
3. **开发流程与协作**:团队开发中的版本控制、代码审查、单元测试等最佳实践,以及如何有效协作。 4. **问题解决**:在项目开发过程中遇到的实际问题,如性能瓶颈、并发控制、数据一致性等,以及解决方案。 5. *...
这本书深入探讨了J2EE(Java 2 Platform, Enterprise Edition)技术体系,涵盖了从设计到实现的整个开发过程,旨在帮助读者理解和掌握在企业级应用开发中的最佳实践。 J2EE是Oracle公司推出的一种用于构建分布式、...
**J2EE架构师手册概览** 《J2EE架构师手册》是一本专为有志于成为J2EE架构师的专业人士所编写的指南。它涵盖了从基础概念到高级设计原则的广泛内容,旨在帮助读者提升在企业级Java应用开发中的专业技能。这本书对于...
html css js网页设计,J2EE架构与应用课程设计。HTML、CSS、JS网页设计与J2EE架构与应用课程...通过本课程的学习,学生将了解J2EE的核心组件、技术规范和最佳实践,掌握如何在J2EE平台上开发高效、稳定的企业级应用。
这本书深入探讨了J2EE(Java 2 Platform, Enterprise Edition)平台在构建企业级应用程序中的核心概念、设计原则以及最佳实践。作为一本手册,它旨在帮助读者理解和掌握J2EE体系结构,提升其在复杂企业环境下的软件...
《J2EE架构师手册》是一本专门为J2EE应用领域的技术架构师量身打造的专业指南。这本书深入探讨了如何成为一名成功的J2EE应用程序架构师,涵盖了J2EE平台的核心概念、设计原则、最佳实践以及架构决策的关键点。以下是...