使用过 Flickr、GMail、Google Suggest 或 Google Maps 的任何用户都会意识到,将出现一种新型的动态 Web 应用程序。这些应用程序的外观和作用与传统的桌面应用程序非常类似,它们不依赖于插件或特定于浏览器的功能。传统的 Web 应用程序是一组 HTML 页,必须重新装入这些页面,才能更改内容的任何部分。在过去几年间,诸如 JavaScript 和层叠样式表 (CSS) 之类的技术已经发展成熟,可以有效地使用这些技术创建在所有主要浏览器上运行的动态性很强的 Web 应用程序。本文档及其相关的解决方案将详细论述这几种技术,目前您可以使用这些技术使 Web 应用程序成为内容更丰富、交互性更强的应用程序,就像桌面应用程序一样。
HTML 页使用 JavaScript 可以异步调用装入该页的服务器并获取 XML 文档。然后 JavaScript 可以使用 XML 文档更新或修改 HTML 页的文档对象模型 (DOM)。最近使用“异步 JavaScript 和 XML”(AJAX) 术语来描述此交互模型。
AJAX 不是一项新兴技术,对于在 Windows 平台上开发 Internet Explorer (IE) 的开发者来说,多年来他们一直在使用这些技术。直到最近,这项技术才被称为 Web 远程处理或远程脚本。一段时间以来,Web 开发者还结合使用插件、Java Applet 和隐藏框架来仿真此交互模型。而最近发生的变化就是:在所有平台的主流浏览器中都包含了对 XMLHttpRequest
对象的支持。真正的神奇之处在于产生了 JavaScript XMLHttpRequest
对象。虽然未在正式的 JavaScript 规范中指定此对象,但是目前所有的主流浏览器都支持它。与当前一代浏览器(如 Firefox、IE 和 Safari)中的 JavaScript 和 CSS 支持稍有不同之处在于:它们是可管理的。如果要求您支持较旧的浏览器,则 AJAX 可能不是您的最佳选择。
基于 AJAX 的客户端的独特之处在于,客户端包含了作为 JavaScript 嵌入的特定于页面的控制逻辑。页面基于事件(如装入的文档、鼠标单击、焦点更改,甚至是计时器)与 JavaScript 进行交互。通过 AJAX 交互,可以清晰地将表示逻辑与数据分开。与每次要显示一个变化时必须重新装入整个页面相比,HTML 页可以根据需要装入很小的数据片段。AJAX 需要使用一种不同的服务器端体系结构来支持此交互模型。传统的服务器端 Web 应用程序着重为产生服务器调用的每个客户端事件生成 HTML 文档,并且在每次响应时客户端都会刷新并重新呈现完整的 HTML 页。内容丰富的 Web 应用程序着重于获取 HTML 文档的客户端,该客户端充当一个模板或容器,根据客户端事件使用从服务器端组件检索的 XML 数据在其中添加内容。
一些使用 AJAX 交互的用例包括:
此列表并不详尽,但是它表明了利用 AJAX 交互,Web 应用程序可以实现比以往更多的功能。尽管该技术有许多引人注目的优点,但是也存在一些缺点:
XMLHttpRequest
对象的标准化:XMLHttpRequest
还不是 JavaScript 规范的一部分,这表明其行为会随着客户端而发生变化。随着开发者编写使用 AJAX 交互模型的应用程序的经验日益丰富,使用 AJAX 技术构建的各种框架和模式可能会纷纷出现。在 AJAX 交互中强调“以部分更改适用全局更改”的框架仍然为时尚早,本文档及其关联的解决方案着重说明了现有的 Java 2 Enterprise Edition (J2EE) 技术(如 Servlet、JavaServer Pages、JavaServer Faces 和 Java 标准标记库 (JSTL))目前是如何支持 AJAX 交互的。
到目前为止,我们已经论述了 AJAX 的含义以及存在的一些疑难问题,现在让我们将所有内容结合起来演示支持 AJAX 的 J2EE 应用程序。
让我们来看一个示例,Web 应用程序包含了一个静态 HTML 页,或者用 JSP 技术生成的 HTML 页,该页包含了一个 HTML 表单,要求服务器端逻辑验证表单数据而不刷新页面。名为 ValidateServlet
的服务器端 Web 组件 (Servlet) 将提供验证逻辑。下图描述了提供验证逻辑的 AJAX 交互的详细信息。
下面的项表示了 AJAX 交互的设置,如上图显示的那样。
XMLHttpRequest
对象。XMLHttpRequest
对象发出调用。ValidationServlet
进行处理。ValidationServlet
返回包含结果的 XML 文档。XMLHttpRequest
对象调用 callback()
函数并处理结果。调用 JavaScript 函数是事件产生的结果。在本例中,函数 validate()
会映射到链接或表单组件上的 onkeyup
事件。
<input type="text" size="20" id="userid" name="id" onkeyup="validate();">
每次在表单字段中按下键时,上面的表单元素都将调用 validate()
。
初始化并配置 XMLHttpRequest
对象。
var req; function validate() { var idField = document.getElementById("idField"); var url = "validate?id=" + escape(idField.value); if (window.XMLHttpRequest) { req = new XMLHttpRequest(); } else if (window.ActiveXObject) { req = new ActiveXObject("Microsoft.XMLHTTP"); } req.open("GET", url, true); req.onreadystatechange = callback; req.send(null); }
validate()
函数会初始化 XMLHttpRequest
对象。open 方法需要三个参数,即表示要使用的 HTTP 方法的 GET 或 POST 的 url 字符串、目标 URL 的字符串以及表明是否发出异步调用的布尔值。如果将交互设置为异步 (true
),则必须指定回调函数。此交互的回调函数是使用语句 req.onreadystatechange =callback;
设置的。有关详细信息,请参见标题为 XMLHttpRequest
对象调用 callback()
函数并处理结果的部分。
执行到语句 req.send(null);
时,将发出调用。对于 HTTP 获得的内容可能是 null
或空值。为 XMLHttpRequest
对象调用此函数时,会调用初始化对象期间设置的 URL。在本示例中,是以 URL 参数的形式包含传递的数据 (id
)。
当请求为幂等时(意味着两个重复的请求将返回相同的结果),请使用 HTTP GET。使用 HTTP GET 方法时,包括转义的 URL 参数在内的 URL 长度受某些浏览器以及服务器端 Web 容器的限制。将数据发送到影响服务器端应用程序状态的服务器时,应该使用 HTTP POST 方法。HTTP POST 要求使用以下语句为 XMLHttpRequest
对象设置 Content-Type
头:
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); req.send("id=" + escape(idTextField.value));
通过 JavaScript 发送表单值时,应该考虑字段值的编码。JavaScript 包括一个 escape()
函数,应该使用该函数确保对本地化的内容进行正确编码并且正确转义特殊字符。
4. 请求由 ValidationServlet
进行处理。
映射到 URI“验证”的 Servlet 会检查用户 ID 是否在用户数据库中。
Servlet 就像处理任何其他 HTTP 请求那样处理 XMLHttpRequest
。下面的示例显示了一个服务器,该服务器从请求中提取 id
参数,并验证是否使用了该参数。
public class ValidationServlet extends HttpServlet { private ServletContext context; private HashMap users = new HashMap(); public void init(ServletConfig config) throws ServletException { this.context = config.getServletContext(); users.put("greg","account data"); users.put("duke","account data"); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String targetId = request.getParameter("id"); if ((targetId != null) && !users.containsKey(targetId.trim())) { response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); response.getWriter().write("valid "); } else { response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); response.getWriter().write("invalid "); } } }
在本示例中,用一个简单的 HashMap
来包含用户,并假定已提交了用户 id "duke"。
5. ValidationServlet
返回 XML 文档。
用户 id "duke" 在 users HashMap
中的用户 id 列表中。ValidationServlet 会将一个 XML 文档写入包含值为 "invalid" 的 "message" 元素的响应。更复杂的用例可能需要 DOM、XSLT 或其他 API 才能生成响应。
response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); response.getWriter().write("invalid ");
开发者需要注意的两点是:Content-Type
需要设置为 "text/xml";Cache-Control
需要设置为 "no-cache"。XMLHttpRequest
对象只处理 Content-Type
为 "text/xml" 的请求;在重复请求相同的 URL(包括 URL 参数)却返回不同的响应时,将 Cache-Control
设置为 "no-cache" 会阻止浏览器在本地缓存响应。
6. XMLHttpRequest
对象调用 callback()
函数并处理结果。
当 XMLHttpRequest
对象的 readyState
发生更改时,XMLHttpRequest
对象被配置为调用 callback()
函数。假定发出了对 ValidationServlet 的调用,并且 readyState
为 "4",表示 XMLHttpRequest
调用完成。HTTP 状态代码为 "200",表示 HTTP 交互成功。
function callback() { if (req.readyState == 4) { if (req.status == 200) { // update the HTML DOM based on whether or not message is valid } } }
浏览器保留被显示文档的对象表示(称为文档对象模型 (DOM))。HTML 页中的 JavaScript 可以访问 DOM,并且可以通过 API 在装入页面后修改 DOM。
请求成功后,JavaScript 代码会修改 HTML 页的 DOM。使用 req.responseXML
(其中 req
是 XMLHttpRequest
对象)的 JavaScript 代码,可以使用从 ValidationServlet 检索的 XML 文档的对象表示。DOM API 为 JavaScript 提供了一种方法,可以导航文档内容并使用该内容修改 HTML 页的 DOM。通过调用 req.responseText
检索的文档的字符串表示。现在,让我们通过查看从 ValidateServlet
返回的以下 XML 文档,了解如何在 JavaScript 中使用 DOM API。
<message> valid </message>
上面的示例是一个简单的 XML 片段,它包含了 message
元素(只是字符串 "valid" 或 "invalid")的发送方。更高级的样例可能包含了显示给用户的多条消息和有效名称。
function parseMessage() { var message = req.responseXML.getElementsByTagName("message")[0]; setMessage(message.childNodes[0].nodeValue); }
parseMessages()
函数会处理从 ValidationServlet
检索的 XML 文档。此函数将使用 message
元素的值调用 setMessage()
以更新 HTML DOM。
JavaScript 可以使用许多 API 获得对 HTML DOM 中任何元素的引用。获得元素引用的建议方法是调用 document.getElementById("userIdMessage")
,其中 "userIdMessage" 是在 HTML 文档中出现的元素的 id 属性。通过对元素的引用,现在可以使用 JavaScript 修改元素的属性,修改元素的样式属性,或者添加、删除或修改子元素。
更改元素的主体内容的一种常用方法是为元素设置 innerHTML
属性,如以下示例所示。
<script type="text/javascript"> function setMessage(message) { var userMessageElement = document.getElementById("userIdMessage"); userMessageElement.innerHTML = "<font color=\"red\">" + message + " </font>"; } </script> <body> <div id="userIdMessage"></div> </body>
在设置 innerHTML
之后,会立即重新呈现 HTML 页中受影响的部分。如果 innerHTML
属性包含诸如 <image>
或 <iframe>
之类的元素,则会同时获取并呈现那些元素指定的内容。
使用此方法的一个主要缺点是:设置为 <div> 元素主体的 HTML 将被硬编码为 JavaScript 中的字符串,包括其他标记,如 <font>
元素。在字符串表示中如果混有 JavaScript 代码,则会使页面难以理解和编辑。
另一种修改 HTML DOM 的方法是动态创建新元素,并将它们作为子级元素附加到目标元素中,如下例所示。
<script type="text/javascript"> function setMessage(message) { var userMessageElement = document.getElementById("userIdMessage"); var userIdMessageFont = document.getElementById("userIdMessageFont"); var messageElement = document.createTextNode(message); if (userMessageElement.childNodes[0]) { // update the elements userIdMessageFont.replaceChild(messageElement, userIdMessageFont.childNodes[0]); } else { // create the new elements var fontElement = document.createTextNode("font"); fontElement.setAtribute("id", "userIdMessageFont"); fontElement.setAtribute("color", "red"); userMessageElement.appendChild(fontElement); fontElement.appendChild(messageElement); } } </script> <body> <div id="userIdMessage"></div> </body>
上述代码样例显示了如何使用 JavaScript DOM API 以编程方式创建元素或更改元素。不同浏览器中的 JavaScript DOM API 支持可能是不同的,因此在开发应用程序时需要小心。
我们已经看到了可能有许多 AJAX 交互可以解决的问题。J2EE 技术为开发和部署使用 API 的基于 AJAX 的应用程序结合使用 HTTP 处理、数据库、Web 服务、XML 处理和业务对象提供了良好的基础。通过更好地了解此交互模型,当前应用程序可以具有更强的交互性,为最终用户提供更佳的体验。
本文档描述了常见用例、涉及的技术,并分析了 AJAX 交互。有关更具体的示例,请参阅各项解决方案。
在 Apple Developer 连接中包含了有关 XMLHttpRequest
对象的很好文档。
W3C 定义的 JavaScript DOM 捆绑。
有关在浏览器中提供 JavaScript 和 CSS 支持的重要参考资料,请访问 http://www.quirksmode.org。
Jesse James Garrett 对 AJAX 的定义。