Ajax或异步JavaScript和XML是Web应用程序开发的一种方法,它使用客户端脚本与Web服务器交换数据。 结果,可以动态更新网页,而无需刷新整个页面即可中断交互流程。 使用Ajax,您可以创建更丰富,更动态的Web应用程序用户界面,以接近本机桌面应用程序的即时性和可用性。
Ajax不是一种技术,而是一种模式 -一种识别和描述有用的设计技术的方法。 在许多开发人员才刚开始意识到Ajax的意义上,它是新的,但是实现Ajax应用程序的所有组件都已经存在了几年。 当前的嗡嗡声是由于2004和2005年出现了一些基于Ajax技术的出色的动态Web UI,最著名的是Google的GMail和Maps应用程序以及照片共享网站Flickr。 这些UI足够具有开创性,以致于一些开发人员将其称为“ Web 2.0”,从而引起了人们对Ajax应用程序的关注。
在本系列中,我将为您提供开始使用Ajax开发自己的应用程序所需的所有工具。 在第一篇文章中,我将解释Ajax背后的概念,并演示为基于Java的Web应用程序创建Ajax接口的基本步骤。 我将使用代码示例来演示使Ajax应用程序如此动态的服务器端Java代码和客户端JavaScript。 最后,我将指出Ajax方法的一些缺陷,以及在创建Ajax应用程序时应考虑的更广泛的可用性和可访问性问题。
更好的购物车
您可以使用Ajax增强传统的Web应用程序,通过消除页面加载来简化交互。 为了说明这一点,我将使用一个购物车的简单示例,该购物车会在添加商品时动态更新。 集成到在线商店后,该方法将使用户能够继续浏览并将商品添加到购物车中,而无需在每次单击后都等待完整页面更新。 尽管本文中的某些代码特定于购物车示例,但是所说明的技术可以应用于任何Ajax应用程序。 清单1显示了购物车示例使用的相关HTML代码。 在整篇文章中,我将再次引用此HTML。
清单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往返
Ajax交互从名为XMLHttpRequest
JavaScript对象开始。 顾名思义,它允许客户端脚本执行HTTP请求,并且将解析XML服务器响应。 此Ajax往返的第一步是创建XMLHttpRequest
实例。 然后在XMLHttpRequest
对象上设置用于请求的HTTP方法( GET
或POST
)和目标URL。
现在,还记得Ajax中的第一个a代表异步吗? 当您发送HTTP请求时,您不希望浏览器在等待服务器响应时会徘徊。 相反,您希望它继续对用户与页面的交互作出React,并在最终到达服务器时处理服务器的响应。 为此,可以向XMLHttpRequest
注册一个回调函数,然后异步调度XMLHttpRequest
。 然后,控制权返回到浏览器,但是当服务器的响应到达时,将调用回调函数。
在Java Web服务器上,请求到达的方式与其他HttpServletRequest
。 解析请求参数之后,该servlet调用必要的应用程序逻辑,将其响应序列化为XML,然后将其写入HttpServletResponse
。
回到客户端,现在调用在XMLHttpRequest
上注册的回调函数来处理服务器返回的XML文档。 最后,响应于来自服务器的数据,使用JavaScript来操纵页面HTML DOM,从而更新用户界面。 图1是Ajax往返的序列图。
图1. Ajax往返
![Ajax往返的序列图](https://i-blog.csdnimg.cn/blog_migrate/ef7b83b3a13397c653b1dcc0ce18ce14.gif)
现在,您已经具有Ajax往返的高级视图,我将放大该过程中的每个步骤。 如果您失去位置,请参考图1 -由于Ajax方法的异步特性,该序列并不完全简单。
调度XMLHttpRequest
我将从Ajax序列的开头开始:从浏览器创建和调度XMLHttpRequest
。 不幸的是,创建XMLHttpRequest
的方法因浏览器而异。 清单2中JavaScript函数消除了这些依赖于浏览器的皱纹,检测出当前浏览器的正确方法,并返回一个可供使用的XMLHttpRequest
。 最好将其视为样板代码:只需将其复制到JavaScript库中,并在需要XMLHttpRequest
时使用即可。
清单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;
}
稍后,我将讨论处理不支持XMLHttpRequest
浏览器的技术。 现在,这些示例假定清单2中的newXMLHttpRequest
函数将始终返回XMLHttpRequest
实例。
回到购物车示例场景,我想在用户单击目录项的“添加到购物车”按钮时调用Ajax交互。 名为addToCart()
的onclick
处理函数负责通过Ajax调用来更新购物车的状态(请参见清单1 )。 如清单3所示, addToCart()
需要做的第一件事是通过调用清单2中的newXMLHttpRequest()
函数来获取XMLHttpRequest
的实例。接下来,它注册一个回调函数来接收服务器的响应(我将说明)稍后对此进行详细介绍;请参见清单6 )。
因为请求将修改服务器上的状态,所以我将使用HTTP POST
来完成任务。 通过POST
发送数据需要三个步骤。 首先,我需要打开一个与我正在与之通信的服务器资源的POST
连接-在这种情况下,这是一个映射到URL cart.do
的servlet。 接下来,我在XMLHttpRequest
上设置一个标头,表明请求的内容是表单编码的数据。 最后,我以表单编码的数据作为正文发送请求。
清单3将这些步骤放在一起。
清单3.调度添加到购物车的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);
}
至此,您已经看到了设置Ajax往返的第一部分-即创建和分配来自客户端的HTTP请求。 接下来是用于处理请求的Java servlet代码。
Servlet请求处理
使用servlet处理XMLHttpRequest
与处理来自浏览器的常规HTTP请求基本相同。 可以使用HttpServletRequest.getParameter()
调用来获取POST请求正文中发送的表单编码数据。 Ajax请求与来自应用程序的常规Web请求一起参与同一HttpSession
。 这对于示例购物车场景很有用,因为它使我可以将用户的购物车状态封装在JavaBean中,并将该状态持久保存到请求之间的会话中。
清单4是一个简单servlet的一部分,该servlet处理Ajax更新购物车的请求。 从用户会话中检索Cart
Bean,并根据请求参数更新其状态。 然后,将Cart
序列化为XML,并将该XML写入ServletResponse
。 将响应的内容类型设置为application/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)) {
// 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()
产生的时间戳。
清单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>
如果您可以从下载小节中获得应用程序源代码中的Cart.java,您会发现XML只是通过将字符串附加在一起而产生的。 尽管足以满足本示例的要求,但这几乎是从Java代码生成XML的最糟糕的方法。 我将在本系列的下一部分中提出一些更好的方法。
现在,您知道了CartServlet
如何响应XMLHttpRequest
。 接下来的事情是返回客户端,在这里您可以看到如何使用XML响应来更新页面状态。
使用JavaScript进行响应处理
XMLHttpRequest
的readyState
属性是一个数字值,用于提供请求生命周期的状态。 它从“未初始化”的0变为“完成”的4。 每次readyState
更改时,都会触发readystatechange
事件,并onreadystatechange
通过onreadystatechange
属性附加的处理函数。
在清单3中 ,您看到了如何调用getReadyStateHandler()
函数来创建处理函数。 然后将此处理程序函数分配给onreadystatechange
属性。 getReadyStateHandler()
利用了以下事实:函数是JavaScript中的一流对象。 这意味着函数可以是其他函数的参数,也可以创建和返回其他函数。 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 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);
}
}
}
}
关于getReadyStateHandler()
getReadyStateHandler()
是一段相对复杂的代码,尤其是如果您不习惯阅读JavaScript。 折衷方案是,通过在JavaScript库中包含此功能,您可以简单地处理Ajax服务器响应,而不必处理XMLHttpRequest
的内部。 重要的是您了解如何在自己的代码中使用getReadyStateHandler()
。
在清单3中 ,您看到getReadyStateHandler()
调用方式如下: handlerFunction = getReadyStateHandler(req, updateCart)
。 在这种情况下,由getReadyStateHandler()
返回的函数将检查变量req
的XMLHttpRequest
是否已完成,然后使用响应XML调用名为updateCart
的函数。
提取购物车数据
清单7是updateCart()
本身的代码。 该函数使用DOM调用询问购物车XML文档,并更新Web页面(请参见清单1 )以反映新的购物车内容。 这里集中于用于从XML DOM提取数据的调用。 检查cart
元素上的已generated
属性,该属性是将cart
Cart
序列化为XML时创建的时间戳,以确保较新的购物车数据不会覆盖较新的购物车数据。 Ajax请求本质上是异步的,因此此检查可防止出现顺序错误的服务器响应。
清单7.更新页面以反映购物车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");
}
至此,尽管您可能想使Web应用程序运行并看到运行的效果,但Ajax往返的全面浏览已经完成(请参阅下载部分)。 该示例非常简单,有很多改进的余地。 例如,我包含了服务器端代码以从购物车中删除商品,但无法从UI中访问它。 作为一个很好的练习,请尝试在应用程序的现有JavaScript代码上构建以实现此功能。
使用Ajax的挑战
与任何技术一样,有很多方法可以使Ajax出错。 我在这里讨论的一些问题目前尚缺乏简单的解决方案,但随着Ajax的成熟,这些问题将得到改善。 随着开发人员社区获得开发Ajax应用程序的经验,将记录最佳实践和指南。
XMLHttpRequest的可用性
Ajax开发人员面临的最大问题之一是当XMLHttpRequest
不可用时如何响应。 尽管大多数现代浏览器都支持XMLHttpRequest
,但始终会有少数用户不支持浏览器,或者其浏览器安全设置会阻止使用XMLHttpRequest
。 如果要开发要在公司Intranet上部署的Web应用程序,则可以奢侈地指定支持哪些浏览器,并假定XMLHttpRequest
始终可用。 但是,如果要在公共Web上进行部署,则必须意识到通过假定XMLHttpRequest
可用,可能会阻止较旧的浏览器,残疾人专用浏览器或手持设备上的轻量级浏览器的用户使用您的应用程序。
因此,您应该努力使您的应用程序“正常降级”,并在没有XMLHttpRequest
支持的浏览器中保持功能。 在购物车示例中,降级应用程序的最佳方法是使“添加到购物车”按钮执行常规的表单提交,刷新页面以反映购物车的更新状态。 一旦页面被加载,就可以通过JavaScript将Ajax行为添加到页面,仅当XMLHttpRequest
可用时,才将JavaScript处理函数附加到每个“添加到购物车”按钮上。 另一种方法是在用户登录时检测XMLHttpRequest
,然后根据需要提供应用程序的Ajax版本或基于表单的常规版本。
可用性问题
围绕Ajax应用程序的一些可用性问题更为笼统。 例如,让用户知道他们的输入已经注册很重要,因为沙漏光标和旋转浏览器“ throbber”的通常反馈机制不适用于XMLHttpRequest
。 一种技术是用“正在更新...”类型的消息替换“提交”按钮,以便用户在等待响应时不会重复单击按钮。
另一个问题是用户可能不会注意到他们正在查看的页面部分已更新。 您可以通过使用各种视觉技术来巧妙地将用户的视线吸引到页面的更新区域来缓解此问题。 使用Ajax更新页面所引起的其他问题包括“破坏”浏览器的后退按钮,以及地址栏中的URL不能反映页面的整个状态,从而无法添加书签。 请参阅相关主题的专门针对Ajax应用程序可用性问题的文章部分。
服务器负载
代替基于常规表单的Ajax UI,实现Ajax UI可能会大大增加对服务器的请求数量。 例如,常规的Google Web搜索会在用户提交搜索表单时在服务器上造成一次点击。 但是,会尝试自动填写搜索字词的Google建议会在用户键入时向服务器发送多个请求。 开发Ajax应用程序时,请注意要发送到服务器的请求数以及由此引起的服务器负载。 您可以通过在客户端上缓存请求并在客户端中缓存服务器响应(如果适用)来减轻服务器负载。 您还应该尝试设计Ajax Web应用程序,以便可以在客户端上执行尽可能多的逻辑,而无需与服务器联系。
处理异步
非常重要的一点是要理解,不能保证XMLHttpRequest
会按其分派的顺序完成。 确实,您应该假定他们不会这样做,并且牢记这一点来设计您的应用程序。 在购物车示例中,使用了最后更新的时间戳来确保较新的购物车数据不会被较旧的数据覆盖(请参见清单7 )。 这种非常基本的方法适用于购物车场景,但可能不适用于其他场景。 在设计时考虑如何处理异步服务器响应。
结论
现在,您应该对Ajax的基本原理有很好的了解,并且对参与Ajax交互的客户端和服务器端组件有一点点了解。 这些是基于Java的Ajax Web应用程序的构建块。 另外,您应该了解Ajax方法随附的一些高级设计问题。 创建成功的Ajax应用程序需要一种整体方法-从UI设计到JavaScript设计再到服务器端体系结构-但现在您应该掌握考虑这些其他方面所需的核心Ajax知识。
如果您对使用此处演示的技术编写大型Ajax应用程序的复杂性感到畏缩,则有个好消息。 正如Struts,Spring和Hibernate之类的框架已经演变为从Servlet API和JDBC的底层细节中抽象出Web应用程序开发一样,工具箱似乎也可以简化Ajax开发。 其中一些仅专注于客户端,提供了向页面添加视觉效果或简化XMLHttpRequest
的使用的简便方法。 其他人则走得更远,提供了从服务器端代码自动生成Ajax接口的方法。 这些框架为您带来了繁重的工作,因此您可以采用更高级的方法来进行Ajax开发。 我将在本系列中研究其中的一些。
Ajax社区正在快速发展,那里有大量有价值的信息。 在这个系列阅读下一篇文章之前,我建议您咨询中列出的文章相关的主题部分,特别是如果你是新来的Ajax或客户端开发。 您还应该花一些时间研究示例源代码,并考虑改进方法。
在本系列的下一篇文章中,我将更详细地讨论XMLHttpRequest
API,并提出从JavaBeans轻松创建XML的方法。 我还将向您展示用于Ajax数据传输的XML替代方法,例如JSON(JavaScript对象表示法)轻量级数据交换格式。
翻译自: https://www.ibm.com/developerworks/java/library/j-ajax1/index.html