灵魂-放水

为学日益,为道日损。

BlogJava 首页 新随笔 联系 聚合 管理
  296 Posts :: 10 Stories :: 274 Comments :: 0 Trackbacks

Patrick Gan (patgan@us.ibm.com), 应用程序卓越创新服务中心的高级 IT 专家, IBM

Asynchronous JavaScript + XML (Ajax)是个相当新的术语(有些人说它是旧酒装新瓶),在不同的 Web 开发社区中,都引起了很大的争议,其中包括 Java EE 社区。Ajax 技术通过消除过多的 Web 页面刷新,提高了应用程序的可用性。而且 Ajax 二者通吃的技术,同时利用了客户端和服务器端代码,向 Web 用户呈现了几乎无缝的用户界面。Ajax 被鼓吹成 Web 开发复兴(或称为 Web 2.0)的一个主要推动者。

作为用心的 Java EE 开发人员,您可能已经阅读了许多关于 Ajax 的 how-to 文章,并对它会给应用程序带来的可能改进而兴奋。但是 Ajax 基于异步通信的模式要怎样才适合您的 Java EE 应用程序呢?这篇文章通过研究在 Java EE 应用程序的设计、开发、执行和测试各阶段引入 Ajax 会带来的影响,将帮助您回答这个问题。我的目的不是不鼓励使用 Ajax 或者暗示您可能遇到的问题是 Ajax 技术固有的问题。相反,我是为了帮助您规划并减轻这些问题,好让您更有效而顺利地利用 Ajax。

解决设计缺陷

相当一段时间以来,Java 社区一直在努力把好的设计模式应用到与 Web 有关的应用程序开发上。其中使用最广泛的一个模式就是模型-视图-控制器(MVC)。一些开放源码框架,例如 Apache Struts,就基于这个设计模式/架构(请参阅 参考资料)。MVC 的众多优势包括:问题隔离 和减少冗余代码。

问题隔离在整个应用程序架构中使用预先协商好的接口,从而让应用程序开发项目中的每个开发人员都专注于自己特定的角色。例如,模型层的开发人员侧重于 JDBC、企业 JavaBean(EJB)组件或者与底层数据持久技术接口的 Java 类这一类的技术。视图层开发人员侧重于 Java 服务器页面(JSP)技术、标记库和其他与表示有关的技术。控制器层隔离及协调模型和视图,把进入的请求路由到后端持久性调用,同时维护问题的清晰隔离。图 1 演示了 MVC 架构:


图 1. MVC 架构
MVC 架构

把 Ajax 引入 Java EE Web 应用程序对于问题的隔离(以及开发人员角色的隔离)是有意义的。在某些情况下,Ajax 会把大量 JavaScript 代码带回视图层(JSP)页面。表 1 描述了没有 Ajax 的视图层,还指出了需要的代码(假设控制器层由 servlet 实现,视图层由 JSP 技术实现。(在下一节 处理开发困境 我将解释同步和异步请求的区别。)


表 1. 没有 Ajax 的 MVC:与典型的视图层序列有关的代码数量

序列 说明 要求代码?
在调用同步请求之前 准备表单提交需要的脚本代码
调用异步请求 由按钮或链接调用引起表单提交;DOM 元素的值自动设置到 HttpRequest(通过 GETPOST)。 否:所需要的只是调用页面提交的途径。
处理同步请求的响应 在服务器端代码执行完成后,通常会向 JSP 发送回一个对象(通过 HttpRequest 或保存在 HttpSession 中)。这时,在 JSP 中可以通过 HttpRequestHttpSession 访问这个对象(通过脚本或某些标记库),只需要编写极少的脚本就可以显示对象的内容。 是:最少的脚本。

对比表 1 和表 2,表 2 描述了 Ajax 的 MVC 视图层,同样假设控制层由 servlet 实现,视图层由 JSP 技术实现。


表 2. 有 Ajax 的 MVC:与典型的视图层序列有关的代码数量

序列 说明 需要代码?
在调用异步请求之前 需要用 JavaScript 代码检索出 Ajax 调用需要的 DOM 元素的值。
调用异步请求 需要用 JavaScript 代码创建 XMLHTTPRequest 并把(以前搜集的)DOM 元素值与之关联并发送(XMLHTTPRequest.send())。
处理异步请求的响应 在服务器端代码执行完成后,要用 JavaScript 代码得到请求(从 XML 响应流中)并把值相应地填充到适当的 DOM 元素。

可以看出,由于使用了 Ajax,视图层的脚本编写量增加了,从而导致三个明显缺陷:

  • JSP 要求大量的 JavaScript 代码。
  • 这个设计破坏了角色问题的隔离。
  • 设计重新带回了单一 JSP(模式 1 方法:一堆 HTML、CSS 代码、图片和脚本代码),这是一种反模式,极难阅读和维护(请参阅 参考资料)。

有几个选项可以避免或者至少减轻这些设计缺陷:

  • 设计时脑子里记着重用:不幸的是,编写特定于 Ajax 支持的代码通常很难避免。请计划和设计脚本代码,以便能够最大限度地重用它。

  • 采用客户端 MVC 方法:可以合并使用客户端 MVC 方法,详见 Dave Crane 等编写的 Ajax in Action(请参阅 参考资料)。这种方法可以促进问题的隔离,但是增加了复杂性,所以使用的时候要仔细考虑。

  • 使用 Ajax 框架:存在多个开放源码的 Ajax 框架,例如 Direct Web Remoting (DWR)(请参阅 参考资料),它们做了很好的工作,可以用最少的编码,就把 Ajax 模式集成到 Java EE 应用程序。

  • 重新评估设计的正确性:实际上,Ajax 为 Web 应用程序提供了桌面应用程序的属性。如果一个 Web 应用程序中大多数客户端交互都利用 Ajax,那么这个应用程序可能最好设计成桌面应用程序。

处理开发困境

在 Java Web 开发中使用 Ajax 时,重要的是完整理解同步异步 通信模型的区别(请参阅 参考资料)。对异步通信模型支持的缺乏,会对客户端开发、与 Web 框架的集成、标记库的使用、IDE 的使用以及线程的行为有影响。

在同步请求/响应通信模型中,总是浏览器(与 Web 服务器、应用服务器或 Web 应用程序相对)发起请求(通过 Web 用户)。接着,Web 服务器、应用服务器或 Web 应用程序响应进入的请求。在处理同步请求/响应对期间,用户不能继续使用浏览器。

图 2 中的序列图演示了传统 Web 应用程序的同步通信模型。请注意在服务器的生命线上,来自客户机的数据提交和服务器端的处理是紧密耦合的。


图 2. 同步通信序列
同步通信序列

在异步请求/响应通信模型中,浏览器(通过 Web 用户)到 Web 服务器、应用服务器或 Web 应用程序的通信(以及反过来)是解耦的。在异步请求/响应对的处理中,Web 用户在当前异步请求被处理时还可以继续使用浏览器。一旦异步请求处理完成,异步响应就被通信(从 Web 服务器、应用服务器或 Web 应用程序)回客户机页面。典型情况下,在这个过程中,调用对 Web 用户没有影响;他们不需要等候响应。

图 3 的序列图演示了异步通信模型。请注意第一个 dataSubmission (由服务器端处理)和第一个返回的 dataSubmission,两个都用红圈圈上了。这些序列是解耦的。这个图示还强调了一个重要方面(后面将详细介绍,请参阅 线程问题):在这个模型中,可以发生多个提交(线程)。


图 3. 异步通信序列
异步通信序列

客户端影响

在向 Web 应用程序引入 Ajax 时,开发团队需要注意几个风险,主要与生成的 HTML 页面及其与浏览器的交互方式有关。这些问题在 Chris Laffra 两部分的 Considering Ajax 系列中有详细介绍(请参阅 参考资料)。有些需要记住的要点是:

  • 可能没打开脚本功能:出于各种原因,在许多用户的浏览器上禁止了 JavaScript 支持。

  • 跨浏览器支持增加了代码需求:支持多种浏览器和多个浏览器版本的应用程序,要求的脚本代码可能会增多,因为浏览器解释 DOM 对象的方式有细微的差异(所以操作这些元素的 JavaScript 代码也有差异)。

  • JavaScript 不安全:在多数浏览器中,可以选择查看源代码选项,查看到与 HTML 页面关联的 JavaScript 源代码。在使用 Ajax 模式时,要确保脚本代码中实现的逻辑不是敏感逻辑。

与 Web 框架集成

试着把 Ajax 开发与所选的 Java EE Web 框架集成,是很自然的。但是有些 Java EE Web 框架对异步通信模型提供直接可以使用的支持。要体会这个事实的意义,需要理解 servlet 处理同步和异步通信的方式。图 4 显示了处理同步请求的传统 servlet 序列:


图 4. 处理同步请求的 Servlet 序列
传统 Servlet 序列(同步)

图 4 对于 Java EE Web 开发人员来说应当相当熟悉。来自浏览器的请求先由控制器 servlet 的 service() 处理。servlet 可以从 HttpRequest 检索到需要的任何值(以参数的形式或者以属性的形式)。一旦控制器处理完成,结果就发送回 HttpRequest (或 HttpSession),而 RequestDispatcher 则把控制转发(或包含)回页面。

图 5 显示了处理异步请求的 servlet 序列:


图 5. 处理异步请求的 Servlet 序列
Servlet 序列(异步)

图 5 中的序列与同步序列略有不同。来自浏览器的请求先由控制器 servlet 的 service() 处理。servlet 可以从 HttpRequest 检索到需要的任何值(以参数的形式或者以属性的形式)。一旦控制器处理完成,HttpServletResponse 的内容类型必须设置成 XML。而且,控制器逻辑的结果要用 PrintWriter 写入。这时,RequestDispatcher 的使用被越过。

这个(异步序列)恰恰是多数 Java EE Web 框架不支持的,从而造成与 Ajax 的集成非常困难。不支持异步通信模型的 Portlet 和 JavaServer Faces (JSF)框架面临着同样的问题。

克服这个问题也有一些选项:

  • 与 Web 框架并存: 不必等待内置的 Ajax 支持或者在所选的框架中强行实现 Ajax 支持,可以使用独立的 servlet 来处理全部异步请求。DWR 就使用了这种方式。这种方式的不足在于 Ajax 请求不能方便地利用框架的特性。

  • 与 Web 框架集成:通过使用免费的扩展或编写定制的扩展,可以设计出与所选 Web 框架集成的途径。

  • 迁移到支持 Ajax 的框架:更新的框架开始支持异步通信模型。其中一个是 Apache Shale(请参阅 参考资料)。

使用标记库

大量使用标记库(taglib)在 Java 的 Web 应用程序开发中是很普遍的。像许多 Java EE Web 框架一样,有些标记库现在也不支持异步通信模型,没有把通过 XMLHttpRequest 提交的数据转换成 HttpServletRequest (以及反过来)的途径。实际上,不支持异步通信的标记库,在 Ajax XMLHttpRequest 调用期间不工作。可用的选项有:

  • 放弃使用不支持异步模型的标记库:把现在由标记库生成的代码迁移到 HTML/JavaScript 代码。(如果 Web 应用程序高度依赖标记库,这种方式最终会造成视图层页面尺寸的增大。)

  • 解决问题:使用已经解决了这个问题的 Ajax 框架。示例之一就是 DWR(请参阅 ExecutionContext.forwardToString())。 在这种情况下,可以继续使用以前一直使用的标记库。

  • 使用支持 Ajax 的标记库:使用支持异步模型的标记库,例如 Ajax JSP 标记库(AjaxTags)(请参阅 参考资料)。

用 IDE 进行开发与调试

有许多 JavaScript 调试工具可以帮助开发人员开发 JavaScript 解决方案。但是,传统的 Java 开发环境不允许检查 XMLHTTPRequest 和与 Ajax 相关的其他东西的值。

一个解决方案是利用 AJAX Toolkit Framework(ATF)(请参阅 参考资料)。ATF 是个 Eclipse 插件,带有增强的 JavaScript 编辑特性,例如编辑时语法检查,内嵌的 Mozilla Web 浏览器,内嵌的 DOM 浏览器,内嵌的 JavaScript 调试器。ATF 还包含 Personality Builder 功能,这个功能可以帮助任何 Ajax 运行时框架构建 IDE 特性,从而加入到 ATF 支持的运行时环境集中。

线程问题

在典型的同步 Web 应用程序中,有些领域对按钮或链接点击要求更长一点的处理时间。没有耐心和没经验的 Web 用户通常会不止一次地点击按钮或链接,以为可以帮助加快处理速度,从而引发多重表单提交。其他时候,用户认为需要双击(就像桌面应用程序那样)。Web 应用程序中的多重表单提交在某些情况下是无害的。而在其他情况下,副作用可能造成严重的线程问题或争用情况(此时多个线程竞争执行一个代码块)。例如,在银行应用程序中多次点击转帐按钮,可能造成不希望的多次转帐。

既支持同步通信模型又支持异步通信模型的 Web 应用程序会发现,如果它的功能没有正确分析和规划,那么自己就处在了相似的困境中。支持两种通信模型的应用程序在某个页面上可能混合了服务器端调用(即,或者同步,或者异步,或者混合了同步和异步)。就像在多重点击场景中一样,异步调用可能处理得慢些。如果应用程序不做预防,用户可能会在异步线程正在处理时又调用了一个同步调用,因为页面没有刷新,所以无法阻止页面上的进一步活动。结果是两个线程并发处理。虽然不是由 Web 页面上的同一按钮或链接引发,这类情况还是会造成服务器端的线程问题(与多重点击问题类似)。

例如,以图 6 所示的银行应用程序的转账页面为例:


图 6. 转帐示例
转账示例

对于这个示例,用红色表示的转账按钮引发一个 Ajax 调用。退出链接(黄色)引发同步调用。如果不耐烦或没有经验的用户点击了红色按钮之后接着又点击黄色链接(假设两个链接在代码中有共同的路径),就会发生争用情况。

一般来说,有两种方式可以避免这类情况发生。第一种方式是客户端解决方案。一旦点击了一个链接或按钮,就用 JavaScript 确保禁止后续的页面提交,直到当前线程执行完成。第二个解决方案是允许多线程提交,但是依赖于服务器端代码中的同步来避免争用情况。如果引入同步来解决这个问题,请记住 Java EE Web 组件(servlet、portlet、JSF 等)是多线程的。要当心大段代码的同步(特别是与请求/响应生命周期处理有关的代码)。在效果上,同步的误用,会把应用程序变成单线程应用程序,从而降低吞吐率。

 

克服性能缺陷

使用 Ajax 还有可能影响基于 Java EE Web 的应用程序的性能。允许每个请求上有额外线程的可能性,可能会影响两个资源。

首先,servlet 容器的线程池 可能受到影响。线程池指定 Web 容器中允许并发运行的线程的最大数量。每个客户机请求需要一个线程。但是,一个客户机请求不一定等于一个用户请求。浏览器可能为一个用户请求要求多个客户机请求。例如,提交表单的一个用户可能要求多个客户机请求(其中包含提交表单的值、检索 GIF 文件、检索 JavaScript 文件、检索 CSS 文件)。如果允许并发地提交同步和异步请求,就意味着每个用户请求至少要支持多出一个的线程消耗(用于 Ajax 请求)。虽然为每个用户请求多增加一个线程的可能性看起来不多,但是当应用程序处在负载之下时,影响就明显了(这时每个用户请求多出的一个额外线程乘上平均用户数量)。显示,这种情况有可能影响 servlet 容器的性能。

另一个可能受影响的资源是数据库连接池。典型的 Java EE Web 应用程序支持一个用户请求的两类序列:浅(shallow)请求和 深(deep)请求。浅请求是执行服务器端代码但是不访问持久性存储(例如数据库)就完成请求的 Web 页面发出的请求。深请求是执行服务器端代码并访问持久性存储才能完成请求的 Web 页面发出的请求。

在深请求序列中(假设需要数据库连接),数据库连接池的这些方面可能会由于允许多个线程而受到影响:

  • 等待连接的线程的平均数量
  • 以毫秒为单位的连接的平均等候时间
  • 连接被使用的平均时间

所以,可能需要提高连接池的平均大小或连接数量。

对付测试

Java 开发人员一直在不断地努力为 Java SE 和 Java EE 代码提供单元测试工具。由于 Ajax 的引入造成浏览器内的 JavaScript 增多,对可靠的 JavaScript 单元测试框架也提出了要求。现在可用的框架有 JsUnit、Selenium 和 HttpUnit(请参阅 参考资料)。

这些框架提供了为 JavaScript 函数开发单元测试的工具,可以操纵 Web 页面上的 DOM 元素。它们允许把单元测试组织成测试套件。Selenium 的浏览器兼容性测试特性允许在不同的浏览器和操作系统上测试 JavaScript 函数。它利用 JavaScript 和 Iframe 在浏览器中嵌入了测试自动引擎。这项技术应当可以在任何支持 JavaScript 的浏览器中工作,对于支持多个浏览器和浏览器版本的应用程序来说特别有用。Selenium 和 JsUnit 都支持持续集成:可以把 JavaScript 单元测试和测试套件集成到自动构建过程。

结束语

把 Ajax —— 就像其他技术或模式一样 —— 引入 Java EE 应用程序,也有它的优势和不足。本文提供了把 Ajax 集成到 Java EE Web 应用程序的概述。Ajax 的异步通信模型与传统 Java EE Web 应用程序内置就支持的同步模型有很大不同。为了避免盲目,请确保在采用 Ajax 之前对潜在的问题领域有全面的事前规划。

Java EE 框架对 Ajax 的支持和工具在不断改进。未来有望会有框架自带的 Ajax 支持来降低集成的复杂性。基于 JSF 的 Apache Shale 和基于 servlet 的 DWR 是两个希望您保持关注的框架。



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1155119

posted on 2006-09-08 15:12 放水老倌 阅读(270) 评论(0)  编辑  收藏 所属分类: J2EE

只有注册用户登录后才能发表评论。


网站导航: