1、面对Java 开发人员的 Ajax:构建动态的 Java 应用程序Ajax 为愈加好的 Web 应用程序铺平了道路文档选项打印本页将此页作为电子邮件发送讨论样例代码级别: 中级Philip McCarthy(), 软件开发顾问, 独立咨询顾问 年 10 月 20 日在 Web 应用程序开发中,页面重载循环是最大的一个使用障碍,对于 Java 开发人员来说也是一个严峻的挑战。在这个系列中,作者 Philip McCarthy 简介了一个创建动态应用程序体验的开创性方式。Ajax(异步 JavaScript 和 XML)是一个编程技术,它允许为基于 Java 的 Web 应用程序把 Java 技术
2、、XML 和 JavaScript 组合起来,从而打破页面重载的范式。Ajax(即异步 JavaScript 和 XML)是一个 Web 应用程序开发的伎俩,它采取客户端脚本与 Web 服务器互换数据。因此,无须采取会中断交互的完整页面刷新,就能够动态地更新 Web 页面。使用 Ajax,能够创建愈加丰富、愈加动态的 Web 应用程序用户界面,其即时性与可用性甚至能够接近本机桌面应用程序。Ajax 不是一项技术,而更像是一个模式 一个识别和描述有用的设计技术的方式。Ajax 是新颖的,因为许多开发人员才刚才开始懂得它,不过所有实现 Ajax 应用程序的组件都已经存在若干年了。它目前受到重视是因
3、为在 和 年出现了某些基于 Ajax 技术的非常棒的动态 Web UI,最知名的就是 谷歌 的 GMail 和 Maps 应用程序,以及照片共享站点 Flickr。这些用户界面具备足够的开创性,有些开发人员称之为“Web 2.0”,因此对 Ajax 应用程序的兴趣飞速上升。在这个系列中,我将提供使用 Ajax 开发应用程序需要的所有工具 。在第一篇文章中,我将解释 Ajax 背后的概念,演示为基于 Java 的 Web 应用程序创建 Ajax 界面的基本步骤。我将使用代码示例演示让 Ajax 应用程序如此动态的服务器端 Java 代码和客户端 JavaScript。最后,我将指出 Ajax 方
4、式的某些不足,以及在创建 Ajax 应用程序时应当考虑的某些更广的可用性和访问性问题。愈加好的购物车能够用 Ajax 增强老式的 Web 应用程序,通过消除页面装入从而简化交互。为了演示这一点,我采取一个简单的购物车示例,在向里面添加项目时,它会动态更新。这项技术假如整合到在线商店,那么用户能够连续地浏览和向购物车中添加项目,而无须在每次点击之后都等候完整的页面更新。虽然这篇文章中的有些代码特定于购物车示例,不过演示的技术能够应用于任何 Ajax 应用程序。清单 1 显示了购物车示例使用的有关 HTML 代码,整篇文章中都会使用这个 HTML。清单1. 购物车示例的有关片断Name Descr
5、iption Price . Hat Stylish bowler hat $19.99 Add to Cart . Total cost: $0.00回页首Ajax 往返过程Ajax 交互开始于叫作XMLHttpRequest的 JavaScript 对象。顾名思义,它允许客户端脚本执行 HTTP 祈求,并解析 XML 服务器响应。Ajax 往返过程的第一步是创建XMLHttpRequest的实例。在XMLHttpRequest对象上设置祈求使用的 HTTP 措施(GET或POST)以及目标 URL。目前,您还记得 Ajax 的第一个a是代表异步(asynchronous)吗?在发送 HTT
6、P 祈求时,不想让浏览器挂着等候服务器响应。相反,您想让浏览器继续对用户与页面的交互进行响应,并在服务器响应抵达时再进行处理。为了实现这个要求,能够在XMLHttpRequest上注册一个回调函数,然后异步地分派XMLHttpRequest。然后控制就会返回浏览器,当服务器响应抵达时,会调用回调函数。在 Java Web 服务器上,祈求同其他HttpServletRequest同样抵达。在解析了祈求参数之后,servlet 调用必要的应用程序逻辑,把响应序列化成 XML,并把 XML 写入HttpServletResponse。回到客户端时,目前调用注册在XMLHttpRequest上的回调函
7、数,处理服务器返回的 XML 文档。最后,依照服务器返回的数据,用 JavaScript 操纵页面的 HTML DOM,把用户界面更新。图 1 是 Ajax 往返过程的次序图。图 1. Ajax 往返过程目前您对 Ajax 往返过程有了一个高层面的认识。下面我将放大其中的每一步骤,进行更详细的观测。假如过程中迷了路,请回头看图 1 因为 Ajax 方式的异步性质,因此次序并非十分简单。回页首分派 XMLHttpRequest我将从 Ajax 序列的起点开始:创建和分派来自浏览器的XMLHttpRequest。不幸的是,不一样的浏览器创建XMLHttpRequest的措施各不相同。清单 2 的
8、JavaScript 函数消除了这些依赖于浏览器的技巧,它能够检测目前浏览器要使用的正确方式,并返回一个能够使用的XMLHttpRequest。最佳是把它当作辅助代码:只要把它拷贝到 JavaScript 库,并在需要XMLHttpRequest的时候使用它就能够了。清单 2. 创建跨浏览器的 XMLHttpRequest/* * Returns a new XMLHttpRequest object, or false if this browser * doesnt support it */function newXMLHttpRequest() var xmlreq = false;
9、if (window.XMLHttpRequest) / Create XMLHttpRequest object in non-Microsoft browsers xmlreq = new XMLHttpRequest(); else if (window.ActiveXObject) / Create XMLHttpRequest via MS ActiveX try / Try to create XMLHttpRequest in later versions / of Internet Explorer xmlreq = new ActiveXObject(Msxml2.XMLHT
10、TP); catch (e1) / Failed to create required ActiveXObject try / Try version supported by older versions / of Internet Explorer xmlreq = new ActiveXObject(Microsoft.XMLHTTP); catch (e2) / Unable to create an XMLHttpRequest with ActiveX return xmlreq; 稍后我将讨论处理那些不支持XMLHttpRequest的浏览器的技术。目前,示例假设清单 2 的ne
11、wXMLHttpRequest函数总能返回XMLHttpRequest实例。返回示例的购物车场景,我想要当用户在目录项目上点击 Add to Cart 时开启 Ajax 交互。名为addToCart()的onclick处理函数负责通过 Ajax 调用来更新购物车的状态(请参阅清单 1)。正如清单 3 所示,addToCart()需要做的第一件事是通过调用清单 2 的newXMLHttpRequest()函数得到XMLHttpRequest对象。接下来,它注册一个回调函数,用来接收服务器响应(我稍后再详细解释这一步;请参阅清单 6)。因为祈求会修改服务器上的状态,因此我将用 HTTPPOST做这
12、个工作。通过POST发送数据要求三个步骤。第一,需要打开与要通信的服务器资源的POST连接 在这个示例中,服务器资源是一个映射到 URLcart.do的 servlet。然后,我在XMLHttpRequest上设置一个头,指明祈求的内容是表单 编码的数据。最后,我用表单编码的数据作为祈求体发送祈求。清单 3 把这些步骤放在了一起。清单 3. 分派 Add to Cart XMLHttpRequest/* * Adds an item, identified by its product code, to the shopping cart * itemCode - product code o
13、f the item to add. */function addToCart(itemCode) / Obtain an XMLHttpRequest instance var req = newXMLHttpRequest(); / Set the handler function to receive callback notifications / from the request object var handlerFunction = getReadyStateHandler(req, updateCart); req.onreadystatechange = handlerFun
14、ction; / Open an HTTP POST connection to the shopping cart servlet. / Third parameter specifies request is asynchronous. req.open(POST, cart.do, true); / Specify that the body of the request contains form data req.setRequestHeader(Content-Type, application/x-www-form-urlencoded); / Send form encoded
15、 data stating that I want to add the / specified item to the cart. req.send(action=add&item=+itemCode);这就是建立 Ajax 往返过程的第一部分,即创建和分派来自客户机的 HTTP 祈求。接下来是用来处理祈求的 Java servlet 代码。回页首servlet 祈求处理用 servlet 处理XMLHttpRequest,与处理一般的浏览器 HTTP 祈求同样。能够用HttpServletRequest.getParameter()得到在 POST 祈求体中发送的表单编码数据。Ajax 祈
16、求被放进与来自应用程序的常规 Web 祈求同样的HttpSession中。对于示例购物车场景来说,这很有用,因为这让我能够把购物车状态封装在 JavaBean 中,并在祈求之间在会话中维持这个状态。清单 4 是处理 Ajax 祈求、更新购物车的简单 servlet 的一部分。Cartbean 是从用户会话中取得的,并依照祈求参数更新它的状态。然后Cart被序列化成 XML,XML 又被写入ServletResponse。重要的是把响应的内容类型设置为application/xml,否则XMLHttpRequest不会把响应内容解析成 XML DOM。清单 4. 处理 Ajax 祈求的 serv
17、let 代码public void doPost(HttpServletRequest req, HttpServletResponse res) throws java.io.IOException Cart cart = getCartFromSession(req); String action = req.getParameter(action); String item = req.getParameter(item); if (action != null)&(item != null) / Add or remove items from the Cart if (add.equ
18、als(action) cart.addItem(item); else if (remove.equals(action) cart.removeItems(item); / Serialize the Carts state to XML String cartXml = cart.toXml(); / Write XML to response. res.setContentType(application/xml); res.getWriter().write(cartXml);清单 5 显示了Cart.toXml()措施生成的示例 XML。它很简单。请注意cart元素的generat
19、ed属性,它是System.currentTimeMillis()生成的一个时间戳。清单 5. Cart 对象的XML 序列化示例 Hat 2 Chair 1 Dog 1 假如查看应用程序源代码(能够从下载一节得到)中的 Cart.java,能够看到生成 XML 的方式只是把字符串添加在一起。虽然对这个示例来说足够了,不过对于从 Java 代码生成 XML 来说则是最差的方式。我将在这个系列的下一期中简介某些愈加好的方式。目前您已经懂得了CartServlet响应XMLHttpRequest的方式。下一件事就是返回客户端,查看怎样用 XML 响应更新页面状态。回页首用 JavaScript 进
20、行响应处理XMLHttpRequest的readyState属性是一个数值,它指出祈求生命周期的状态。它从 0(代表“未初始化”)变化到 4(代表“完成”)。每次readyState变化时,readystatechange事件就触发,由onreadystatechange属性指定的事件处理函数就被调用。在清单 3中已经看到了怎样调用getReadyStateHandler()函数创建事件处理函数。然后把这个事件处理函数分派给onreadystatechange属性。getReadyStateHandler()利用了这么一个事实:函数是 JavaScript 中的一级对象。这意味着函数能够是其他
21、函数的参数,也能够创建和返回其他函数。getReadyStateHandler()的工作是返回一个函数,检查XMLHttpRequest是否已经完成,并把 XML 响应传递给调用者指定的事件处理函数。清单 6 是getReadyStateHandler()的代码。清单 6. getReadyStateHandler() 函数/* * Returns a function that waits for the specified XMLHttpRequest * to complete, then passes its XML response to the given handler func
22、tion. * req - The XMLHttpRequest whose state is changing * responseXmlHandler - Function to pass the XML response to */function getReadyStateHandler(req, responseXmlHandler) / Return an anonymous function that listens to the / XMLHttpRequest instance return function () / If the requests status is co
23、mplete if (req.readyState = 4) / Check that a successful server response was received if (req.status = 200) / Pass the XML payload of the response to the / handler function responseXmlHandler(req.responseXML); else / An HTTP problem has occurred alert(HTTP error: +req.status); HTTP 状态码在清单 6 中,检查XMLH
24、ttpRequest的status属性以查看祈求是否成功完成。status包括服务器响应的 HTTP 状态码。在执行简单的GET和POST祈求时,能够假设任何不小于 200 (OK)的码都是错误。假如服务器发送重定向响应(例如 301 或 302),浏览器会透明地进行重定向并从新的位置获取资源;XMLHttpRequest看不到重定向状态码。并且,浏览器会自动添加Cache-Control: no-cache头到所有XMLHttpRequest,这么客户代码永远也不用处理 304(未经修改)服务器响应。有关 getReadyStateHandler()getReadyStateHandler(
25、)是段相对复杂的代码,尤其是假如您不习惯阅读 JavaScript 的话。不过通过把这个函数放在 JavaScript 库中,就能够处理 Ajax 服务器响应,而无须处理XMLHttpRequest的内部细节。重要的是要了解怎样在自己的代码中使用getReadyStateHandler()。在清单 3中看到了getReadyStateHandler()像这么被调用:handlerFunction = getReadyStateHandler(req, updateCart)。在这个示例中,getReadyStateHandler()返回的函数将检查在req变量中的XMLHttpRequest是
26、否已经完成,然后用响应的 XML 调用名为updateCart的函数。提取购物车数据清单 7 是updateCart()自身的代码。函数用 DOM 调用检查购物车的 XML 文档,然后更新 Web 页面(请参阅清单 1),反应新的购物车内容。这里的重点是用来从 XML DOM 提取数据的调用。cart元素的generated属性是在Cart序列化为 XML 时生成的一个时间戳,检查它能够确保新的购物车数据不会被旧的数据覆盖。Ajax 祈求天生是异步的,因此这个检查能够处理服务器响应未按次序抵达的情况。清单 7. 更新页面,反应购物车的 XML 文档function updateCart(car
27、tXML) / Get the root cart element from the document var cart = cartXML.getElementsByTagName(cart)0; / Check that a more recent cart document hasnt been processed / already var generated = cart.getAttribute(generated); if (generated lastCartUpdate) lastCartUpdate = generated; / Clear the HTML list us
28、ed to display the cart contents var contents = document.getElementById(cart-contents); contents.innerHTML = ; / Loop over the items in the cart var items = cart.getElementsByTagName(item); for (var I = 0 ; I items.length ; I+) var item = itemsI; / Extract the text nodes from the name and quantity el
29、ements var name = item.getElementsByTagName(name)0 .firstChild.nodeValue; var quantity = item.getElementsByTagName(quantity)0 .firstChild.nodeValue; / Create and add a list item HTML element for this cart item var li = document.createElement(li); li.appendChild(document.createTextNode(name+ x +quant
30、ity); contents.appendChild(li); / Update the carts total using the value from the cart document document.getElementById(total).innerHTML = cart.getAttribute(total);到此,整个 Ajax 往返过程完成了,不过您也许想让 Web 应用程序运行一下查看实际效果(请参阅下载一节)。这个示例非常简单,有诸多需要改进之处。例如,我包括了从购物车中清除项目标服务器端代码,不过无法从 UI 访问它。作为一个好的练习,请试着在应用程序既有的 Java
31、Script 代码之上构建出能够实现这个功效的代码。回页首使用 Ajax 的挑战就像任何技术同样,使用 Ajax 也有许多犯错的也许性。我目前在这里讨论的问题还缺乏轻易的处理方案,不过会伴随 Ajax 的成熟而改进。伴随开发人员小区增加开发 Ajax 应用程序的经验,将会统计下最佳实践和指南。XMLHttpRequest 的可用性Ajax 开发人员面临的一个最大问题是:在没有XMLHttpRequest可用时该怎样响应?虽然重要的当代浏览器都支持XMLHttpRequest,但仍然有少数用户的浏览器不支持,或者浏览器的安全设置制止使用XMLHttpRequest。假如开发的 Web 应用程序要
32、布署在企业内部网,那么也许拥有指定支持哪种浏览器的权力,从而能够以为XMLHttpRequest总能使用。不过,假如要布署在公共 Web 上,那么就必须当心,假如假设XMLHttpRequest可用,那么就也许会制止那些使用旧的浏览器、残疾人专用浏览器和手持设备上的轻量级浏览器的用户使用您的应用程序。因此,您应当努力让应用程序“平稳降级”,在没有XMLHttpRequest支持的浏览器中也能够工作。在购物车的示例中,把应用程序降级的最佳方式也许是让 Add to Cart 按钮执行一个常规的表单提交,刷新页面来反应购物车更新后的状态。Ajax 的行为应当在页面装入的时候就通过 JavaScri
33、pt 添加到页面,只有在XMLHttpRequest可用时才把 JavaScript 事件处理函数附加到每个 Add to Cart 按钮。另一个方式是在用户登录时检测XMLHttpRequest是否可用,然后对应地提供应用程序的 Ajax 版本或基于表单的一般版本。可用性考虑有关 Ajax 应用程序的某些可用性问题比较普遍。例如,让用户懂得他们的输入已经注册了也许是重要的,因为沙漏光标和 spinning 浏览器的常用反馈机制“throbber”对XMLHttpRequest不合用。一个技术是用“Now updating.”类型的信息替代 Submit 按钮,这么用户在等候响应期间就不会重复
34、单击按钮了。另一个问题是,用户也许没有注意到他们正在查看的页面的某一部分已经更新了。能够使用不一样的可视技术,把用户的眼球带到页面的更新区域,从而缓解这个问题。由 Ajax 更新页面导致的其他问题还包括:“破坏了”浏览器的后退按钮,地址栏中的 URL 也无法反应页面的整个状态,妨碍了设置书签。请参阅参考资料一节,取得专门处理 Ajax 应用程序可用性问题的文章。服务器负载用 Ajax 实现替代一般的基于表单的 UI,会大大提升对服务器发出的祈求数量。例如,一个一般的 谷歌 Web 搜索对服务器只有一个祈求,是在用户提交搜索表单时出现的。而 谷歌 Suggest 试图自动完成搜索术语,它要在用户
35、输入时向服务器发送多个祈求。在开发 Ajax 应用程序时,要注意将要发送给服务器的祈求数量以及由此导致的服务器负荷。减少服务器负载的措施是,在客户机上对祈求进行缓冲并且缓存服务器响应(假如也许的话)。还应当尝试将 Ajax Web 应用程序设计为在客户机上执行尽也许多的逻辑,而无须联系服务器。处理异步非常重要的是,要了解无法确保XMLHttpRequest会按照分派它们的次序完成。实际上,应当假设它们不会按次序完成,并且在设计应用程序时把这一点记在心上。在购物车的示例中,使用最后更新的时间戳来确保新的购物车数据不会被旧的数据覆盖(请参阅清单 7)。这个非常基本的方式能够用于购物车场景,不过也许
36、不适合其他场景。因此在设计时请考虑怎样处理异步的服务器响应。回页首结束语目前您对 Ajax 的基本标准应当有了很好的了解,对参加 Ajax 交互的客户端和服务器端组件也应当有了初步的知识。这些是基于 Java 的 Ajax Web 应用程序的结构块。另外,您应当了解了伴随 Ajax 方式的某些高级设计问题。创建成功的 Ajax 应用程序要求整体考虑,从 UI 设计到 JavaScript 设计,再到服务器端架构;不过您目前应当已经武装了考虑其他这些方面所需要的核心 Ajax 知识。假如使用这里演示的技术编写大型 Ajax 应用程序的复杂性让您以为恐慌,那么有好消息给您。因为 Struts、Sp
37、ring 和 Hibernate 此类框架的发展把 Web 应用程序开发从底层 Servlet API 和 JDBC 的细节中抽象出来,因此正在出现简化 Ajax 开发的工具包。其中有些只侧重于客户端,提供了向页面添加可视效果的简便方式,或者简化了对XMLHttpRequest的使用。有些则走得更远,提供了从服务器端代码自动生成 Ajax 接口的方式。这些框架替您完成了繁重的任务,因此您能够采取更高级的方式进行 Ajax 开发。我在这个系列中将研究其中的某些。Ajax 小区正在迅速前进,因此会有大量有价值的信息涌现。在阅读这个系列的下一期之前,我提议您参考参考资料一节中列出的文章,尤其是假如您
38、是刚接触 Ajax 或客户端开发的话。您还应当花些时间研究示例源代码并考虑某些增强它的方式。在这个系列的下一篇文章中,我将深入讨论XMLHttpRequestAPI,并推荐某些从 JavaBean 以便地创建 XML 的方式。我还将简介替代 XML 进行 Ajax 数据传递的方式,例如 JSON(JavaScript Object Notation)轻量级数据互换格式。回页首下载描述名字大小下载措施Sample codej-ajax1.zip8 KBHTTP有关下载措施的信息参考资料学习 您能够参阅本文在 developerWorks 全球站点上的英文原文。 “Beyond the DOM”(
39、Dethe Elza, developerWorks, 年 5 月):进行 XML 文档访问的有用的 JavaScript 技术。 “AJAX 及使用 E4X 编写 Web 服务脚本,第 1 部分”(Paul Fremantle 和 Anthony Elder,developerWorks, 年 4 月):用 Ajax 在支持 E4X JavaScript 扩展的浏览器中进行 SOAP 调用。 “Ajax: A New Approach to Web Applications”(Jesse James Garrett,Adaptive Path, 年 2 月):简介 Ajax 起源的短文。 T
40、he Java BluePrints Solutions Catalog:简介了 Ajax 在几个常见 Web 应用程序场景中的应用。 AjaxPatterns.org:包括多项改进 Ajax 应用程序的 UI 技术。 XMLHttpRequest Usability Guidelines:对使用 Ajax 提升用户体验的提议。 Ajax Mistakes:Ajax 应用程序应当防止的可用性问题。 Java 技术专区:在这里能够找到有关 Java 编程的各个方面的文章。取得产品和技术 Mozilla Firefox:DOM Inspector 和 JavaScript Debugger 扩展消
41、除了许多 Ajax 开发的痛苦。讨论 参加论坛讨论。 developerWorks blogs:加入 developerWorks 小区。面对 Java 开发人员的 Ajax:Ajax 的 Java 对象序列化在 Ajax 应用程序中序列化数据的五种途径级别: 中级Philip McCarthy(), 软件开发顾问, 独立顾问 年 10 月 24 日假如您正在使用异步 JavaScript 和 XML(Ajax)进行 Java Web 开发,那么您最关心的问题也许就是把数据从服务器传递给客户机。在面对 Java 开发人员的 Ajax系列的第二篇文章中,Philip McCarthy 简介了 Java 对象序列化的五种方式,并提供了选择最适合应用程序的数据格式和技术所需要的所有信息。在这个系列的第一篇文章中,我简介了 Ajax 的结构块: 怎样用 JavaScriptXMLHttpRequest对象从 Web 页面对服务器发送异步祈求。 怎样用 Java servlet 处理和响应祈求(向客户机返回 XML 文档)。 怎样在客户端用响应文档更新页面视图。这一次,我将继续讨论 Ajax 开发的基础知识,不过将侧重于许多 Java Web 开发人员最关心的问题:为客户机生成数据。多数 Java 开发人员