?Web 应用E序开发中Q页面重载@环是最大的一个用障,对于 Java?开发h员来说也是一个严ȝ挑战。在q个pd中,作?Philip McCarthy 介绍了一U创建动态应用程序体验的开创性方式。AjaxQ异?JavaScript ?XMLQ是一U编E技术,它允ؓZ Java ?Web 应用E序?Java 技术、XML ?JavaScript l合hQ从而打破页面重载的范式?/BLOCKQUOTE>
AjaxQ即异步 JavaScript ?XMLQ是一U?Web 应用E序开发的手段Q它采用客户端脚本与 Web 服务器交换数据。所以,不必采用会中断交互的完整面hQ就可以动态地更新 Web 面。?AjaxQ可以创建更加丰富、更加动态的 Web 应用E序用户界面Q其x性与可用性甚臌够接q本机桌面应用程序?/P>
Ajax 不是一Ҏ术,而更像是一?模式 —?一U识别和描述有用的设计技术的方式。Ajax 是新颖的Q因多开发h员才刚刚开始知道它Q但是所有实?Ajax 应用E序的组仉已经存在若干q了。它目前受到重视是因为在 2004 ?2005 q出C一些基?Ajax 技术的非常的动?Web UIQ最著名的就?Google ?GMail ?Maps 应用E序Q以及照片共享站?Flickr。这些用L面具有够的开创性,有些开发h员称之ؓ“Web 2.0”,因此?Ajax 应用E序的兴飞速上升?/P>
在这个系列中Q我提供?Ajax 开发应用程序需要的全部工具 。在W一文章中Q我解?Ajax 背后的概念,演示为基?Java ?Web 应用E序创徏 Ajax 界面的基本步骤。我用代码示例演C Ajax 应用E序如此动态的服务器端 Java 代码和客L JavaScript。最后,我将指出 Ajax 方式的一些不I以及在创?Ajax 应用E序时应当考虑的一些更q的可用性和讉K性问题?/P>
更好的购物R
可以?Ajax 增强传统?Web 应用E序Q通过消除面装入从而简化交互。ؓ了演C一点,我采用一个简单的购物车示例,在向里面d目Ӟ它会动态更新。这Ҏ术如果整合到在线商店Q那么用户可以持l地览和向购物车中d目Q而不必在每次点击之后都等候完整的面更新。虽然这文章中的有些代码特定于购物车示例,但是演示的技术可以应用于M Ajax 应用E序。清?1 昄了购物RCZ使用的有?HTML 代码Q整文章中都会使用q个 HTML?/P>
清单1. 购物车示例的有关片断
<!-- Table of products from store's catalog, one row per item -->
<th>Name</th> <th>Description</th> <th>Price</th> <th></th>
...
<tr>
<!-- Item details -->
<td>Hat</td> <td>Stylish bowler hat</td> <td>$19.99</td>
<td>
<!-- Click button to add item to cart via Ajax request -->
<button onclick="addToCart('hat001')">Add to Cart</button>
</td>
</tr>
...
<!-- Representation of shopping cart, updated asynchronously -->
<ul id="cart-contents">
<!-- List-items will be added here for each item in the cart -->
</ul>
<!-- Total cost of items in cart displayed inside span element -->
Total cost: <span id="total">$0.00</span>
|
Ajax 往q过E?/FONT>
Ajax 交互开始于叫作 XMLHttpRequest
?JavaScript 对象。顾名思义Q它允许客户端脚本执?HTTP hQƈ解析 XML 服务器响应。Ajax 往q过E的W一步是创徏 XMLHttpRequest
的实例。在 XMLHttpRequest
对象上设|请求用的 HTTP ҎQ?CODE>GET ?POST
Q以及目?URL?/P>
现在Q您q记?Ajax 的第一?a 是代?异步QasynchronousQ?/I> 吗?在发?HTTP hӞ不想让浏览器挂着{候服务器响应。相反,您想让浏览器l箋对用户与面的交互进行响应,q在服务器响应到达时再进行处理。ؓ了实现这个要求,可以?XMLHttpRequest
上注册一个回调函敎ͼ然后异步地分z?XMLHttpRequest
。然后控制就会返回浏览器Q当服务器响应到达时Q会调用回调函数?/P>
?Java Web 服务器上Q请求同其他 HttpServletRequest
一样到达。在解析了请求参C后,servlet 调用必要的应用程序逻辑Q把响应序列化成 XMLQƈ?XML 写入 HttpServletResponse
?/P>
回到客户端时Q现在调用注册在 XMLHttpRequest
上的回调函数Q处理服务器q回?XML 文。最后,Ҏ服务器返回的数据Q用 JavaScript 操纵面?HTML DOMQ把用户界面更新。图 1 ?Ajax 往q过E的序图?/P>
?1. Ajax 往q过E?/B>
![Ajax 往q过E的序? src=]()
现在您对 Ajax 往q过E有了一个高层面的认识。下面我放大其中的每一步骤Q进行更详细的观察。如果过E中q了路,请回头看?1 —?׃ Ajax 方式的异步性质Q所以顺序ƈ非十分简单?/P>
分派 XMLHttpRequest
我将?Ajax 序列的v点开始:创徏和分z来自浏览器?XMLHttpRequest
。不q的是,不同的浏览器创徏 XMLHttpRequest
的方法各不相同。清?2 ?JavaScript 函数消除了这些依赖于览器的技巧,它可以检当前浏览器要用的正确方式Qƈq回一个可以用的 XMLHttpRequest
。最好是把它当作辅助代码Q只要把它拷贝到 JavaScript 库,q在需?XMLHttpRequest
的时候用它可以了?/P>
清单 2. 创徏跨浏览器?XMLHttpRequest
/*
* Returns a new XMLHttpRequest object, or false if this browser
* doesn't support it
*/
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;
}
|
E后我将讨论处理那些不支?XMLHttpRequest
的浏览器的技术。目前,CZ假设清单 2 ?newXMLHttpRequest
函数总能q回 XMLHttpRequest
实例?/P>
q回CZ的购物R场景Q我惌当用户在目录目上点?Add to Cart 时启?Ajax 交互。名?addToCart()
?onclick
处理函数负责通过 Ajax 调用来更新购物R的状态(请参?清单 1Q。正如清?3 所C,addToCart()
需要做的第一件事是通过调用清单 2 ?newXMLHttpRequest()
函数得到 XMLHttpRequest
对象。接下来Q它注册一个回调函敎ͼ用来接收服务器响应(我稍后再详细解释q一步;请参?清单 6Q?/P>
因ؓh会修Ҏ务器上的状态,所以我用 HTTP POST
做这个工作。通过 POST
发送数据要求三个步骤。第一Q需要打开与要通信的服务器资源?POST
q接 —?在这个示例中Q服务器资源是一个映到 URL cart.do
?servlet。然后,我在 XMLHttpRequest
上设|一个头Q指明请求的内容是表?~码的数据。最后,我用表单~码的数据作求体发送请求?/P>
清单 3 把这些步骤放在了一赗?/P>
清单 3. 分派 Add to Cart XMLHttpRequest
/*
* Adds an item, identified by its product code, to the shopping cart
* itemCode - product code of 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 = handlerFunction;
// 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 data stating that I want to add the
// specified item to the cart.
req.send("action=add&item="+itemCode);
}
|
q就是徏?Ajax 往q过E的W一部分Q即创徏和分z来自客h?HTTP h。接下来是用来处理请求的 Java servlet 代码?/P>
servlet h处理
?servlet 处理 XMLHttpRequest
Q与处理普通的览?HTTP h一栗可以用 HttpServletRequest.getParameter()
得到?POST h体中发送的表单~码数据。Ajax h被放q与来自应用E序的常?Web h一L HttpSession
中。对于示例购物R场景来说Q这很有用,因ؓq让我可以把购物车状态封装在 JavaBean 中,q在h之间在会话中l持q个状态?/P>
清单 4 是处?Ajax h、更新购物R的简?servlet 的一部分?CODE>Cart bean 是从用户会话中获得的QƈҎh参数更新它的状态。然?Cart
被序列化?XMLQXML 又被写入 ServletResponse
。重要的是把响应的内容类型设|ؓ application/xml
Q否?XMLHttpRequest
不会把响应内容解析成 XML DOM?/P>
清单 4. 处理 Ajax h?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)) {
// Add or remove items from the Cart
if ("add".equals(action)) {
cart.addItem(item);
} else if ("remove".equals(action)) {
cart.removeItems(item);
}
}
// Serialize the Cart's state to XML
String cartXml = cart.toXml();
// Write XML to response.
res.setContentType("application/xml");
res.getWriter().write(cartXml);
}
|
清单 5 昄?Cart.toXml()
Ҏ生成的示?XML。它很简单。请注意 cart
元素?generated
属性,它是 System.currentTimeMillis()
生成的一个时间戳?/P>
清单 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>
|
如果查看应用E序源代码(可以?下蝲 一节得刎ͼ中的 Cart.javaQ可以看到生?XML 的方式只是把字符串添加在一赗虽然对q个CZ来说_了,但是对于?Java 代码生成 XML 来说则是最差的方式。我在q个pd的下一期中介绍一些更好的方式?/P>
现在您已l知道了 CartServlet
响应 XMLHttpRequest
的方式。下一件事是q回客户端,查看如何?XML 响应更新面状态?/P>
?JavaScript q行响应处理
XMLHttpRequest
?readyState
属性是一个数|它指求生命周期的状态。它?0Q代表“未初始化”)变化?4Q代表“完成”)。每?readyState
变化Ӟreadystatechange
事gp发,?onreadystatechange
属性指定的事g处理函数p调用?/P>
?清单 3 中已l看C如何调用 getReadyStateHandler()
函数创徏事g处理函数。然后把q个事g处理函数分配l?onreadystatechange
属性?CODE>getReadyStateHandler() 利用了这样一个事实:函数?JavaScript 中的一U对象。这意味着函数可以是其他函数的参数Q也可以创徏和返回其他函数?CODE>getReadyStateHandler() 的工作是q回一个函敎ͼ?XMLHttpRequest
是否已经完成Qƈ?XML 响应传递给调用者指定的事g处理函数。清?6 ?getReadyStateHandler()
的代码?/P>
清单 6. getReadyStateHandler() 函数
/*
* Returns a function that waits for the specified XMLHttpRequest
* to complete, then passes its XML response to the given handler function.
* 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 request's status is "complete"
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 中,?XMLHttpRequest ?status 属性以查看h是否成功完成?CODE>status 包含服务器响应的 HTTP 状态码。在执行单的 GET ?POST hӞ可以假设M大于 200 QOKQ的码都是错误。如果服务器发送重定向响应Q例?301 ?302Q,览器会透明地进行重定向q从新的位置获取资源Q?CODE>XMLHttpRequest 看不到重定向状态码。而且Q浏览器会自动添?Cache-Control: no-cache 头到所?XMLHttpRequest Q这样客户代码永q也不用处理 304Q未l修改)服务器响应?/P> | |
关于 getReadyStateHandler()
getReadyStateHandler()
是段相对复杂的代码,特别是如果您不习惯阅?JavaScript 的话。但是通过把这个函数放?JavaScript 库中Q就可以处理 Ajax 服务器响应,而不必处?XMLHttpRequest
的内部细节。重要的是要理解如何在自q代码中?getReadyStateHandler()
?/P>
?清单 3 中看C getReadyStateHandler()
像这栯调用Q?CODE>handlerFunction = getReadyStateHandler(req, updateCart)。在q个CZ中,getReadyStateHandler()
q回的函数将查在 req
变量中的 XMLHttpRequest
是否已经完成Q然后用响应?XML 调用名ؓ updateCart
的函数?/P>
提取购物车数?/STRONG>
清单 7 ?updateCart()
本n的代码。函数用 DOM 调用查购物R?XML 文Q然后更?Web 面Q请参阅 清单 1Q,反映新的购物车内宏V这里的重点是用来从 XML DOM 提取数据的调用?CODE>cart 元素?generated
属性是?Cart
序列化ؓ XML 时生成的一个时间戳Q检查它可以保证新的购物车数据不会被旧的数据覆盖。Ajax h天生是异步的Q所以这个检查可以处理服务器响应未按ơ序到达的情c?/P>
清单 7. 更新面Q反映购物R?XML 文
function updateCart(cartXML) {
// Get the root "cart" element from the document
var cart = cartXML.getElementsByTagName("cart")[0];
// Check that a more recent cart document hasn't been processed
// already
var generated = cart.getAttribute("generated");
if (generated > lastCartUpdate) {
lastCartUpdate = generated;
// Clear the HTML list used 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 = items[I];
// Extract the text nodes from the name and quantity elements
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 "+quantity));
contents.appendChild(li);
}
}
// Update the cart's total using the value from the cart document
document.getElementById("total").innerHTML =
cart.getAttribute("total");
}
|
到此Q整?Ajax 往q过E完成了Q但是您可能惌 Web 应用E序q行一下查看实际效果(请参?下蝲 一节)。这个示例非常简单,有很多需要改q之处。例如,我包含了从购物R中清除项目的服务器端代码Q但是无法从 UI 讉K它。作Z个好的练习,误着在应用程序现有的 JavaScript 代码之上构徏够实现这个功能的代码?/P>
使用 Ajax 的挑?/FONT>
像M技术一P使用 Ajax 也有许多出错的可能性。我目前在这里讨论的问题q缺乏容易的解决ҎQ但是会随着 Ajax 的成熟而改q。随着开发h员社区增加开?Ajax 应用E序的经验,会记录下最佛_践和指南?/P>
XMLHttpRequest 的可用?/STRONG>
Ajax 开发h员面临的一个最大问题是Q在没有 XMLHttpRequest
可用时该如何响应Q虽然主要的C览器都支持 XMLHttpRequest
Q但仍然有少数用L览器不支持Q或者浏览器的安全设|阻止?XMLHttpRequest
。如果开发的 Web 应用E序要部|在企业内部|,那么可能拥有指定支持哪种览器的权力Q从而可以认?XMLHttpRequest
总能使用。但是,如果要部|在公共 Web 上,那么必d心,如果假设 XMLHttpRequest
可用Q那么就可能会阻止那些用旧的浏览器、残疾h专用览器和手持讑֤上的轻量U浏览器的用户用您的应用程序?/P>
所以,您应当努力让应用E序“^E降U”,在没?XMLHttpRequest
支持的浏览器中也能够工作。在购物车的CZ中,把应用程序降U的最好方式可能是?Add to Cart 按钮执行一个常规的表单提交Q刷新页面来反映购物车更新后的状态。Ajax 的行为应当在面装入的时候就通过 JavaScript d到页面,只有?XMLHttpRequest
可用时才?JavaScript 事g处理函数附加到每?Add to Cart 按钮。另一U方式是在用L录时?XMLHttpRequest
是否可用Q然后相应地提供应用E序?Ajax 版本或基于表单的普通版本?/P>
可用性考虑
关于 Ajax 应用E序的某些可用性问题比较普遍。例如,让用L道他们的输入已经注册了可能是重要的,因ؓ沙漏光标?spinning 览器的常用反馈机制“throbber”对 XMLHttpRequest
不适用。一U技术是用“Now updating...”类型的信息替换 Submit 按钮Q这L户在{候响应期间就不会反复单击按钮了?/P>
另一个问题是Q用户可能没有注意到他们正在查看的页面的某一部分已经更新了。可以用不同的可视技术,把用L眼球带到面的更新区域,从而缓解这个问题。由 Ajax 更新面造成的其他问题还包括Q“破坏了”浏览器的后退按钮Q地址栏中?URL 也无法反映页面的整个状态,妨碍了设|书{。请参阅 参考资?/FONT> 一节,获得专门解决 Ajax 应用E序可用性问题的文章?/P>
服务器负?/STRONG>
?Ajax 实现代替普通的Z表单?UIQ会大大提高Ҏ务器发出的请求数量。例如,一个普通的 Google Web 搜烦Ҏ务器只有一个请求,是在用户提交搜烦表单时出现的。?Google Suggest 试图自动完成搜烦术语Q它要在用户输入时向服务器发送多个请求。在开?Ajax 应用E序Ӟ要注意将要发送给服务器的h数量以及由此造成的服务器负荷。降低服务器负蝲的办法是Q在客户Z对请求进行缓冲ƈ且缓存服务器响应Q如果可能的话)。还应该试?Ajax Web 应用E序设计为在客户Z执行可能多的逻辑Q而不必联l服务器?/P>
处理异步
非常重要的是Q要理解无法保证 XMLHttpRequest
会按照分z֮们的序完成。实际上Q应当假讑֮们不会按序完成Qƈ且在设计应用E序时把q一点记在心上。在购物车的CZ中,使用最后更新的旉x保新的购物车数据不会被旧的数据覆盖Q请参阅 清单 7Q。这个非常基本的方式可以用于购物车场景,但是可能不适合其他场景。所以在设计时请考虑如何处理异步的服务器响应?/P>
l束?/FONT>
现在您对 Ajax 的基本原则应当有了很好的理解Q对参与 Ajax 交互的客L和服务器端组件也应当有了初步的知识。这些是Z Java ?Ajax Web 应用E序的构造块。另外,您应当理解了伴随 Ajax 方式的一些高U设计问题。创建成功的 Ajax 应用E序要求整体考虑Q从 UI 设计?JavaScript 设计Q再到服务器端架构;但是您现在应当已l武装了考虑其他q些斚w所需要的核心 Ajax 知识?/P>
如果使用q里演示的技术编写大?Ajax 应用E序的复杂性让您觉得恐慌,那么有好消息l您。由?Struts、Spring ?Hibernate q类框架的发展把 Web 应用E序开发从底层 Servlet API ?JDBC 的细节中抽象出来Q所以正在出现简?Ajax 开发的工具包。其中有些只侧重于客LQ提供了向页面添加可视效果的便方式,或者简化了?XMLHttpRequest
的用。有些则走得更远Q提供了从服务器端代码自动生?Ajax 接口的方式。这些框架替您完成了J重的Q务,所以您可以采用更高U的方式q行 Ajax 开发。我在这个系列中研I其中的一些?/P>
Ajax C正在快速前q,所以会有大量有价值的信息涌现。在阅读q个pd的下一期之前,我徏议您参?参考资?/FONT> 一节中列出的文章,特别是如果您是刚接触 Ajax 或客L开发的话。您q应当花些时间研I示例源代码q考虑一些增强它的方式?/P>
在这个系列的下一文章中Q我深入讨?XMLHttpRequest
APIQƈ推荐一些从 JavaBean 方便地创?XML 的方式。我q将介绍替代 XML q行 Ajax 数据传递的方式Q例?JSONQJavaScript Object NotationQ轻量数据交换格式?/P>
下蝲
描述 |
名字 |
大小 |
下蝲Ҏ |
Sample code |
j-ajax1.zip |
8 KB |
FTP |
参考资?
学习
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文?BR>
- ?A >Beyond the DOM”(Dethe ElzaQ?developerWorksQ?005 q?5 月)Q进?XML 文档讉K的有用的 JavaScript 技术?BR>
- ?A >AJAX 及?E4X ~写 Web 服务脚本Q第 1 部分”(Paul Fremantle ?Anthony ElderQdeveloperWorksQ?005 q?4 月)Q用 Ajax 在支?E4X JavaScript 扩展的浏览器中进?SOAP 调用?BR>
- ?A >Ajax: A New Approach to Web Applications”(Jesse James GarrettQAdaptive PathQ?005 q?2 月)Q介l?Ajax h的短文?BR>
- The Java BluePrints Solutions CatalogQ介l了 Ajax 在几个常?Web 应用E序场景中的应用?BR>
- AjaxPatterns.orgQ包含多Ҏq?Ajax 应用E序?UI 技术?BR>
- XMLHttpRequest Usability GuidelinesQ对使用 Ajax 提高用户体验的徏议?BR>
- Ajax MistakesQAjax 应用E序应当避免的可用性问题?BR>
- Java 技术专?/FONT>Q在q里可以扑ֈ关于 Java ~程的各个方面的文章?BR>
获得产品和技?/B>
讨论
- 加入本文?A href="javascript:void forumWindow()">论坛 ?您也可以通过点击文章剙或者底部的论坛链接参加讨论?
- developerWorks blogsQ加?developerWorks C?/LI>
关于作?/FONT>
 |
|
Philip McCarthy 是一位Y件开发顾问,专攻 Java ?Web 技术。他目前在位?Bristol ?HP 试验室从?Hewlett Packard 数字媒体q_的工作。在最q几q中QPhil 开发了多个采用异步服务器通信?DOM 脚本的富 Web 客户端。他很高兴我们现在有了一个针对它们的名称。可以通过 Phil 的电子邮?philmccarthy@gmail.com 与他联系?/P> |