您的位置:首页 > 编程语言 > Java开发

ajax:构建动态的java应用程序

2007-01-05 15:17 316 查看
Ajax(即异步 JavaScript 和 XML)是一种 Web 应用程序开发的手段,它采用客户端脚本与 Web 服务器交换数据。所以,不必采用会中断交互的完整页面刷新,就可以动态地更新 Web 页面。使用 Ajax,可以创建更加丰富、更加动态的 Web 应用程序用户界面,其即时性与可用性甚至能够接近本机桌面应用程序。
Ajax 不是一项技术,而更像是一个 模式 —— 一种识别和描述有用的设计技术的方式。Ajax 是新颖的,因为许多开发人员才刚刚开始知道它,但是所有实现 Ajax 应用程序的组件都已经存在若干年了。它目前受到重视是因为在 2004 和 2005 年出现了一些基于 Ajax 技术的非常棒的动态 Web UI,最著名的就是 Google 的 GMail 和 Maps 应用程序,以及照片共享站点 Flickr。这些用户界面具有足够的开创性,有些开发人员称之为“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 是代表 异步(asynchronous) 吗?在发送 HTTP 请求时,不想让浏览器挂着等候服务器响应。相反,您想让浏览器继续对用户与页面的交互进行响应,并在服务器响应到达时再进行处理。为了实现这个要求,可以在
XMLHttpRequest
上注册一个回调函数,然后异步地分派
XMLHttpRequest
。然后控制就会返回浏览器,当服务器响应到达时,会调用回调函数。

在 Java Web 服务器上,请求同其他
HttpServletRequest
一样到达。在解析了请求参数之后,servlet 调用必要的应用程序逻辑,把响应序列化成 XML,并把 XML 写入
HttpServletResponse


回到客户端时,现在调用注册在
XMLHttpRequest
上的回调函数,处理服务器返回的 XML 文档。最后,根据服务器返回的数据,用 JavaScript 操纵页面的 HTML DOM,把用户界面更新。图 1 是 Ajax 往返过程的顺序图。

图 1. Ajax 往返过程



现在您对 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
实例。

返回示例的购物车场景,我想要当用户在目录项目上点击 Add to Cart 时启动 Ajax 交互。名为
addToCart()
onclick
处理函数负责通过 Ajax 调用来更新购物车的状态(请参阅 清单 1)。正如清单 3 所示,
addToCart()
需要做的第一件事是通过调用清单 2 的
newXMLHttpRequest()
函数得到
XMLHttpRequest
对象。接下来,它注册一个回调函数,用来接收服务器响应(我稍后再详细解释这一步;请参阅 清单 6)。

因为请求会修改服务器上的状态,所以我将用 HTTP
POST
做这个工作。通过
POST
发送数据要求三个步骤。第一,需要打开与要通信的服务器资源的
POST
连接 —— 在这个示例中,服务器资源是一个映射到 URL
cart.do
的 servlet。然后,我在
XMLHttpRequest
上设置一个头,指明请求的内容是表单 编码的数据。最后,我用表单编码的数据作为请求体发送请求。

清单 3 把这些步骤放在了一起。

清单 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);
}

这就是建立 Ajax 往返过程的第一部分,即创建和分派来自客户机的 HTTP 请求。接下来是用来处理请求的 Java servlet 代码。

servlet 请求处理

用 servlet 处理
XMLHttpRequest
,与处理普通的浏览器 HTTP 请求一样。可以用
HttpServletRequest.getParameter()
得到在 POST 请求体中发送的表单编码数据。Ajax 请求被放进与来自应用程序的常规 Web 请求一样的
HttpSession
中。对于示例购物车场景来说,这很有用,因为这让我可以把购物车状态封装在 JavaBean 中,并在请求之间在会话中维持这个状态。

清单 4 是处理 Ajax 请求、更新购物车的简单 servlet 的一部分。
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>

如果查看应用程序源代码(可以从 j-ajax1.zip 得到)中的 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() 函数

/*
* 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
属性以查看请求是否成功完成。
status
包含服务器响应的 HTTP 状态码。在执行简单的
GET
POST
请求时,可以假设任何大于 200 (OK)的码都是错误。如果服务器发送重定向响应(例如 301 或 302),浏览器会透明地进行重定向并从新的位置获取资源;
XMLHttpRequest
看不到重定向状态码。而且,浏览器会自动添加
Cache-Control: no-cache
头到所有
XMLHttpRequest
,这样客户代码永远也不用处理 304(未经修改)服务器响应。

关于 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
序列化为 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");
}

到此,整个 Ajax 往返过程完成了,这个示例非常简单,有很多需要改进之处。例如,我包含了从购物车中清除项目的服务器端代码,但是无法从 UI 访问它。作为一个好的练习,请试着在应用程序现有的 JavaScript 代码之上构建出能够实现这个功能的代码。

j-ajax1.zip: in my link
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: