Ajax for java developers.建立动态java应用
日期:2008年12月26日 作者:
查看:[大字体 中字体
小字体 ]
作者:Philip McCarthy (philmccarthy@gmail.com)软件开发顾问,独立顾问 翻译:jenniferweng 页面重载循环(page-reload cycle)引出了WEB应用开发中可用性的最大障碍,成为java开发者们一个严重的挑战。在这个系列文章中,作者Philip McCarthy介绍了一个极富创造性的方法来创建动态WEB应用。Ajax(异步javascript和xml)是一种编程技术,结合Java技术、XML和JavaScript来为基于java的WEB应用突破页面重载的模式。 Ajax,或者异步JavaScript和XML,是一种WEB应用开发的方法,它使用了客户端脚本来和WEB服务器交换数据,这样,WEB页面不需要中断交互流进行整页刷新和重载就可以被动态更新。通过Ajax,你能够创建更加丰富,更加动态的WEB应用用户界面,这样的用户界面其及时性和可用性接近于本地桌面应用。Ajax不是一个技术,它更像是一个模式 -- 一种识别和描述有用的设计技巧的方法。许多开发者刚刚开始注意到Ajax,从这个意义上来说它很新。但是所有的用于实现一个Ajax应用的各部分技术都早已存在了好几年。当前的热潮是因为在2004年和2005年,一些很棒的动态WEB UI是基于Ajax技术,其中最著名的是Google的Gmail和地图应用,以及图片共享网站Flickr.这些UI是如此的富于创造性,因此被一些开发者冠以WEB 2.0的名称。因此导致了对Ajax应用的兴趣暴涨起来。 在这个系列中,我将给你在开发自己的Ajax应用时所需要的所有工具。在第一篇文章,我将解释在Ajax名称之后的概念并演示创建一个基于Java的WEB应用的Ajax界面的基本步骤。我将使用代码例子来演示正是服务器端的Java代码和客户端的Javascript代码使得Ajax应用如此动态。最后,我将指出Ajax方法的一些缺陷,以及当你创建Ajax应用时必须考虑的更广泛的可用性和存取性问题。一个更好的购物车 你可以用Ajax来增强传统的Web应用,通过消除页面重载而简化交互。为了证明这一点,我将使用一个简单的例子:当购物项被增加进来的时候购物车被动态更新了。结合到在线商店,这个方法将使用户可以持续浏览和增加购物项到他们的购物车而不需要再每次点击后等待一个整页的更新。尽管在本文中的一些代码是特定于购物车实例,描述的技术可被应用到任何Ajax应用。列表1显示了购物车实例使用的相关HTML代码。在本文的后续部分将会持续的引用这个HTML。 列表 1.购物车实例的相关片段
<!-- 商店目录的产品列表,每项一行--> <th>Name</th> <th>Description</th> <th>Price</th> <th></th> ... <tr> <!-- 产品项详情 --> <td>Hat</td> <td>Stylish bowler hat</td> <td>$19.99</td> <td> <!--点击按钮以通过Ajax请求将购物项增加到购物车--> <button onclick="addToCart('hat001')">Add to Cart</button> </td> </tr> ... <!--购物车的表示法,异步更新 --> <ul id="cart-contents"> <!-- 列表项将在这里被增加为购物车里的每一项 --> </ul> <!--在横跨元素项中显示购物项的总金额 --> Total cost: <span id="total">$0.00</span>
Ajax处理流程 一个Ajax交互开始于一个被称之为XMLHttpRequest的JavaScript对象。正如这个名称所说的,它允许一个客户端的脚本来执行HTTP请求,并解析一个XML服务响应。在Ajax过程的第一步是创建一个XMLHttpRequest实例。用于请求的HTTP方法(GET or POST)以及目标URL将被设置到XMLHttpRequest对象中。 现在,还记得在Ajax标准中的第一个A是表示异步吗?当你发出一个HTTP请求,你无须挂起浏览器以等待服务器响应,相反,你需要它继续对用户的交互做出反应,当服务器的响应最终到达时再处理它。为了实现这一点,你可以用XMLHttpRequest注册一个回叫函数(callback function),继而异步的处理XMLHttpRequest。此时控制回到浏览器,但是当服务器的响应到达时回叫函数将被调用。 在Java Web服务器上,该请求正如其他任何HttpServletRequest一样的到达。当解析了请求参数以后,servlet调用必要的应用逻辑,将它的响应序列化(serialize)为XML,并将它写到HttpServletResponse。 回到客户端,注册到XMLHttpRequest的回叫函数现在被调用来处理由服务器返回的XML文本。最后,用户界面根据对服务器端对数据的响应而被更新了,使用JavaScript来处理页面HTML DOM 图1是Ajax处理流程的序列图 现在你有了一个高层次的Ajax流程视图。我将逐个对每一步骤进行详细放大。如果你遗失方向,回到图1--这个序列并非完全向前,这是因为Ajax方法的异步特性。分发一个XMLHttpRequest 我将开始于Ajax序列的起始:从浏览器创建并分发一个XMLHttpRequest。不幸的是,创建XMLHttpRequest的方法随着浏览器的不同而不同。在列表2种的JavaScript函数抹平了依赖于各浏览器的差异,为当前的浏览器检测正确的方法,并返回一个XMLHttpRequest以备使用,最好想象这是一个样板代码,简单的复制它到你的JavaScript库,并在你需要一个XMLHttpRequest时使用它。 Listing 2. 创建一个跨越浏览器的 XMLHttpRequest
/* * Returns a new XMLHttpRequest object, or false if this browser * doesn't support it *返回一个新的XMLHttpRequest对象,或者为false如果浏览器不支持它 */ function newXMLHttpRequest() { var xmlreq = false; 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.XMLHTTP"); } 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中的newXMLHttpRequest函数总是可以返回一个XMLHttpRequest实例。 回到购物车场景实例,我想在用户为一个购物项点击加入购物车按钮时调用一个Ajax交互。这个onclick处理函数被命名为addToCart(),它负责通过一个Ajax call更新购物车的状态(见列表1)。正如在列表3中所示,addToCart()需要做的第一件事是通过调用列表2中的newXMLHttpRequest函数获得一个XMLHttpRequest的实例,下一步它将注册一个回叫函数以接受服务器端的响应(我将在以后解释这个;见列表6) 因为请求会修改服务器端的状态,我将使用HTTP POST来做这件事。通过POST发送数据需要3步。首先,我需要打开一个到我正在通信的服务器资源的POST连接---在本例中,是一个映射到URL cart.do的servlet。下一步,我将在XMLHttpRequest中设置一个header以表明请求的内容是编码形式(form-encoded)的数据。最后,我将编码形式的数据作为请求的主体部分发出。 列表3给出了所有这些步骤 列表3.分发一个an Add to Cart XMLHttpRequest
/* * 增加一个购物项道购物车,以它的产品代码标识 * itemCode - 增加的产品项代码. */ function addToCart(itemCode) { // 获得一个 XMLHttpRequest 实例 var req = newXMLHttpRequest(); //设置一个句柄函数接受来自请求对象的回叫通知 var handlerFunction = getReadyStateHandler(req, updateCart); req.onreadystatechange = handlerFunction; //打开一个到购物车servlet的HTTP POST连接. //第3个参数指明请求是异步的. req.open("POST", "cart.do", true); // 指明请求体包含形式数据 req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // 发送编码数据表明我想要增加特定项到购物车. req.send("action=add&item="+itemCode); }
现在你可以看到Ajax处理流程的第一部分--从客户端创建和分发HTTP 请求,下一步是Java servlet代码用来处理请求 Servlet请求处理 用一个Servlet处理一个XMLHttpRequest 大体上和处理一个来自浏览器的常规的HTTP请求相同。在POST请求体中的编码形式的数据可以通过调用HttpServletRequest.getParameter()获得。Ajax请求和常规应用的Web请求都包含在同样的HttpSession中。这对于购物车场景的例子来说很有用,因为他允许我将用户的购物车状态封装到一个JavaBean并且在各请求之间将状态保留在会话之中。 列表4是一个简单的servlet中处理Ajax请求以更新购物车的一部分。一个Cart bean被从用户会话中检索到,它的状态根据请求的参数被更新。Cart接着被序列化为XML,XML被写到ServletResponse。设置相应内容的格式为appllication/xml是很重要的,否则XMLHttpRequest将不会将响应内容解析为一个XML DOM 列表4.处理Ajax请求的Servlet编码
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)) { // 从购物车中增加或移去项 if ("add".equals(action)) { cart.addItem(item); } else if ("remove".equals(action)) { cart.removeItems(item); } } // 序列化购物车状态为XML String cartXml = cart.toXml(); // 将 XML写回到 response. res.setContentType("application/xml"); res.getWriter().write(cartXml); }
列表5显示了一个通过Cart.toXml()方法所产生的XML的例子。它是十分直接的。注意在Cart元素所产生的属性是由System.currentTimeMillis().所产生的时间戳 列表5.Cart对象的XML序列化的例子
<?xml version="1.0"?> <cart generated="1123969988414" total="$171.95"> <item code="hat001"> <name>Hat</name> <quantity>2</quantity> </item> <item code="cha001"> <name>Chair</name> <quantity>1</quantity> </item> <item code="dog001"> <name>Dog</name> <quantity>1</quantity> </item> </cart>
如果你看看从download 部分可获得的Cart.java的源代码,你将会看见XML可以通过简单的将字符串添加在一起而产生。尽管对于本例来说足够了,但是这几乎是从Java代码生成XML的最糟糕的方法。我将会在这个系列的下一部分中建议一些更好的方法。 所以现在你知道了CartServlet如何对一个 XMLHttpRequest做出响应。下一件事是返回到客户端,看看XML响应是如何被用户更新页面状态。使用JavaScript处理响应 XMLHttpRequest的readyState属性是一个数值型的值,它给出了请求的生命周期的状态,从0意味着未初始化变化到4意味着完成。每次readyState改变,readystatechange事件发生,通过onreadystatechange属性相连的句柄函数被调用。 在列表3中,你看到了函数getReadyStateHandler()是如何被调用来创建一个句柄函数。句柄函数接着被指定给onreadystatechange属性。getReadyStateHandler()利用了这样一个事实:函数在Javascript中是第一级的对象。这意味着函数可以成为其他函数的参数,也可以创建和返回其他函数。这正是getReadyStateHandler()的工作:返回一个函数检查是否XMLHttpRequest已经结束,并且将XML响应传递给由呼叫者所指定的句柄函数。列表6是getReadyStateHandler()的代码 列表6。 getReadyStateHandler() 函数
/* * 返回一个等待特定XMLHttpRequest的函数 *如果完成,将XML 响应传递给给定的句柄函数. * req - 状态发生改变的那个 XMLHttpRequest * responseXmlHandler - 传递XML响应的目标函数 */ function getReadyStateHandler(req, responseXmlHandler) { // 返回一个匿名函数以监听// XMLHttpRequest 实例 return function () { //如果请求状态是 "complete" if (req.readyState == 4) { // 检查是否一个成功的服务器响应被受到 if (req.status == 200) { //将 响应的XML 负载传递给 // 句柄函数 responseXmlHandler(req.responseXML); } else { //出现了一个 HTTP 问题 alert("HTTP error: "+req.status); } } } }
HTTP状态编码 在列表6中,XMLHttpRequest的状态属性被测试以察看是否请求被成功完成了,状态包含了服务器响应的HTTP状态编码。当完成简单的GET和POST请求时,你能够假定任何不是200(OK)的代码都是一个错误。如果服务器发出了一个重定向响应(例如,301或302),浏览器将透明的跟随重定向并从新的地址获取资源,XMLHttpRequest不会看到重定向状态编码。同样浏览器会自动增加一个Cache-Control:对于所有的l XMLHttpRequests无缓冲标头(no-cache header),因此客户端代码永远不需要处理一个304(未被修改)的服务器响应关于getReadyStateHandler() getReadyStateHandler() 是代码中相对复杂的部分,尤其是如果你并不习惯阅读JavaScript。换而言之,你可以将这个函数包含在你的JavaScript库中,这样你可以简单的处理Ajax服务器响应而无需了解XMLHttpRequest的内部。最重要的是你理解如何在自己的代码中使用getReadyStateHandler() 在列表3,你看到了用如下的方式getReadyStateHandler() 被呼叫:handlerFunction=getReadyStateHandler(req,updateCart) 在本例中由getReadyStateHandler() 所返回的函数将检查是否在变量req中的XMLHttpRequest被完成了,然后用响应的XML呼叫一个名为updateCart的函数获取cart数据 列表7是updateCart()自身的编码。函数用DOM调用来查询购物车XML文档,并更新Web页面(见列表1)以反映新的购物车内容。注意这里用来从XML DOM中抽取数据的呼叫。对cart元素所产生的属性,当Cart被序列化为XML时所产生的时间戳,将被检查以确保新的购物车数据没有被旧的改写。Ajax请求天生是异步的,所以这个检查防止了服务器响应未能按顺序到达的情况。 列表7.更新页面以反映购物车XML 文本
function updateCart(cartXML) { //从文本中获得根 "cart" 元素 var cart = cartXML.getElementsByTagName("cart")[0]; // 检查一个时间更近的购物车文本未被处理 var generated = cart.getAttribute("generated"); if (generated > lastCartUpdate) { lastCartUpdate = generated; // 清除用于显示购物车内容的HTML列表被清除 var contents = document.getElementById("cart-contents"); contents.innerHTML = ""; // 购物车的购物项循环 var items = cart.getElementsByTagName("item"); for (var I = 0 ; I < items.length ; I++) { var item = items[I]; // 从name和quantity元素中获取文本节点 var name = item.getElementsByTagName("name")[0] .firstChild.nodeValue; var quantity = item.getElementsByTagName("quantity")[0] .firstChild.nodeValue; // 为购物项创建并增加一个列表项HTML元素 var li = document.createElement("li"); li.appendChild(document.createTextNode(name+" x "+quantity)); contents.appendChild(li); } } //使用购物车文档中的值更新购物车总数 document.getElementById("total").innerHTML = cart.getAttribute("total"); }
在这里,整个Ajax处理流程的沿途旅程就结束了,然而,你可以获得这个Web应用并使之运行(见download部分)。这个例子非常简单,有很多地方可以改进。作为例子,我包含了服务器端代码以从购物车中移去购物项,但是没有从UI中使用它的方式。作为很好的练习,尝试建立应用的JavaScript代码以完成这个功能。使用Ajax的挑战 正如对于任何技术来说都是一样的,使用Ajax时很多方式都会导致错误。在这里我讨论其中的一些问题,虽然缺乏简单的处理办法,但是当Ajax成熟时会被改进。作为开发者团体在开发Ajax应用时所获得的经验,最好的实践和规则就是记录下来。XMLHttpRequest的可用性 对于Ajax开发者所面对的最大的问题就是如果XMLHttpRequest 不可用时如何响应。虽然大部分现代的浏览器都支持XMLHttpRequest ,总是会有少数用户的浏览器不支持,或者一些浏览器的安全设置阻止了XMLHttpRequest 被使用。如果你开发的WEB应用将被部署到一个公司的Intranet,特别指定哪些浏览器将被支持或者假定XMLHttpRequest 总是可用的也许是件奢侈的事情。如果你部署在公众Web,你必须注意到由于预设XMLHttpRequest 是可用的,你潜在的阻止了那些使用旧浏览器、有缺陷的浏览器或者在手持设备上使用的轻量级浏览器的用户使用你的应用。 因此,你必须渴望使得你的应用"优雅的降级":在不支持XMLHttpRequest 的浏览器上同样起作用。在购物车例子中,最好的降级应用的方法将是让Add to Cart按钮完成一个常规形式的任务,刷新整个页面去反映购物车的更新状态。仅仅在XMLHttpRequest 可用的情况下,当页面被装载,将JavaScript句柄函数连接到每个Add to Cart按钮,Ajax行为将通过JavaScript增加到页面。另一个方法是当用户登录时检测XMLHttpRequest ,然后决定是否用一个Ajax版本的应用还是一个常规的基于form的版本。 可用性的关注 一些Ajax应用的可用性问题是非常普遍的。例如,让用户知道他们的输入是否被登记是很重要的,因为通常的反馈机制如沙漏状的鼠标指针和旋转的浏览器跳动图标并不会应用于XMLHttpRequest 。一个技术是替换Submit按钮代之以“Now updating...”类型的信息,这样用户不会在等待响应时重复的点击。 另一个问题是用户可能没有注意到页面的部分被更新了。你可以通过使用一个视觉上的变化来巧妙的吸引用户的注意力到页面的更新部分以减轻这个问题。另一个使用Ajax更新页面所引起的问题包含打断浏览器的后退按钮。在地址栏的URL不能反映页面的完整状态,阻止了被加上书签。请看资源部分的一些文章特别的说明了Ajax应用的可用性问题服务器负载 用Ajax UI取代一个常规的基于form的UI可能戏剧化的增加了请求的数目。例如,一个常规的google Web搜索将会在用户提交搜索表单时引起服务器上的一次处理。然而google Suggest,试图自动完成用户的搜索,当用户按键时发送了多次请求。当开发一个Ajax应用时,注意你将发送多少请求给服务器,并将增加多少服务器负载。如果可以的话,你可以通过在客户端缓冲请求或者缓冲服务器端响应以减轻服务器的负载。你也应该尝试设计你的Ajax Web应用以使得尽可能多的应用逻辑在客户端被执行,而不是连接到服务器处理异步 没有任何保证XMLHttpRequests 会按他们分发的顺序完成,理解这一点是极其重要的。实际上你应该假设他们不会按顺序完成,并据此设计你的应用。在购物车实例中,一个最后更新的时间戳将被用于确保最新的购物车数据不会被旧的覆盖(见列表7)。这是解决购物车场景的异步问题的根本性工作,但可能并不适合其他情况。在设计时考虑你如何处理服务器响应的异步问题结论 你现在对Ajax的基本原理和参与到ajax交互的客户端-服务器端的细节有了一个很好的理解。这些是建立基于java的Ajax Web应用的构成块。此外,你应该理解一些随着Ajax方法而来的高级设计问题。创建一个成功的Ajax应用需要一个整体的分析,从UI设计到JavaScript设计,到服务器端的构建--但是你现在应该理解Ajax的核心知识以考虑其他的一些方面。 如果你对于用这里所展示的一些技术来写一个大型的Ajax应用的复杂性而感到沮丧,这是一个好消息。正如其他构架如Struts, Spring, 和 Hibernate正在致力于将Web应用开发从低级的细节如Servlet API和JDBC中抽离出来一样,正在出现一些工具包来使Ajax开发变的容易。其中的一些仅集中于客户端,提供增加页面视觉效果或者使得XMLHttpRequest的使用更加流畅(streamlining)。其他的走的更远,提供了工具自动从服务器端代码生成Ajax界面。这些构架大大的减轻了你的负担,因此你能够从一个更高层次来处理Ajax开发。在这个系列中我将考虑到其中的一些。 Ajax团体正在迅速的转移,那里有大量的有价值的信息。在阅读这个系列的下一部分以前,我建议你研究在资源部分列出的文章,尤其是如果你是ajax新手或者客户端开发者。你也应该花点时间来学习实例的源代码,并考虑如何改进它。
上一篇:中文搜索引擎技术揭密:中文分词
下一篇:关于MySQL嵌套查询的技巧
复制本页网址和标题,发送给你QQ/Msn的好友一起分享
特别声明: 本站除部分特别声明禁止转载的专稿外的其他文章可以自由转载,但请务必注明出处和原始作者。文章版权归文章原始作者所有。对于被本站转载文章的个人和网站,我们表示深深的谢意。如果本站转载的文章有版权问题请联系编辑人员,我们尽快予以更正。
[打印本页 ] [关闭窗口 ] 转载请注明来源:http://www.abcdown.net