Ajax 核心 API(即所謂的 XMLHttpRequest)的唯一用途就是發(fā)送 HTTP 請求,在 Web 瀏覽器與服務器之間進行數(shù)據(jù)交換。Web 頁面中運行的 JavaScript 代碼,可以使用 XMLHttpRequest 將該請求參數(shù)提交至服務器端腳本,例如 Servlet 或 JSP 頁面。調(diào)用的 Servlet/JSP 將發(fā)回一個響應,其中包含了一般用于不需刷新整個頁面即可更新用戶查看內(nèi)容的數(shù)據(jù)。此種方法在性能和可用性方面均體現(xiàn)出了獨有的優(yōu)勢,因為這將降低網(wǎng)絡(luò)通信量,而且 Web UI 的使用幾乎與桌面 GUI 一樣。 但是,開發(fā)這種用戶界面并不簡單,因為您必須在客戶端上使用 JavaScript、在服務器端上使用 Java(或等效語言)實施數(shù)據(jù)交換、驗證以及處理。然而,在許多情況下,考慮到將會由此獲得的益處,付出額外精力構(gòu)建一個基于 Ajax 的界面是值得的。 在本文中,我將介紹一種用于在 Ajax 客戶端和服務器之間傳輸數(shù)據(jù)的主要方法,并比較傳統(tǒng) Web 應用程序模型與該 Ajax 模型的不同點。此外,文中還將探討在服務器端與客戶端處理數(shù)據(jù)的技巧。 首先,您將了解如何在客戶端使用 JavaScript 編碼請求對象的參數(shù)。您可以使用所謂的 URL 編碼(Web 瀏覽器使用的默認編碼),或可將請求參數(shù)包含在 XML 文檔中。服務器將處理該請求,并返回一個其數(shù)據(jù)也必須進行編碼的響應。本文將探討 JavaScript Object Notation (JSON) 和 XML,這些都是主要的響應數(shù)據(jù)格式選項。 本文的大部分內(nèi)容將主要介紹 Ajax 應用程序中通常使用的與 XML 相關(guān)的 API。在客戶端,XML API 的作用雖非常有限,但已夠用。在多數(shù)情況下,利用 XMLHttpRequest 即可完成所有必需操作。此外,還可使用 JavaScript 在 Web 瀏覽器中分析 XML 文檔并串行化 DOM 樹。在服務器端,可用于處理 XML 文檔的 API 和框架有很多種。本文將介紹如何使用針對 XML 的標準 Java API 來實施基本任務,該 API 支持 XML 模式、XPath、DOM 以及許多其他標準。 通過本文,您可以了解到在 Ajax 應用程序中實現(xiàn)數(shù)據(jù)交換所用的最佳技巧和最新的 API。其中涉及的示例代碼分別位于以下三個程序包中:util、model 和 feed。util 程序包中的類提供了用于 XML 分析、基于模式的驗證、基于 XPath 的查詢、DOM 串行化以及 JSON 編碼的方法。model 程序包包含的示例數(shù)據(jù)模型可用于從 XML 文檔進行初始化,然后再轉(zhuǎn)換至 JSON 格式。model 目錄中還有一個 Schema 示例,可用于 XML 驗證。feed 程序包中的類可用于模擬數(shù)據(jù)饋送,其通過 Ajax 每 5 秒檢索一次來獲得信息,以刷新 Web 頁面。本文闡釋了如何通過終止未完成的 Ajax 請求并在使用完 XMLHttpRequest 對象后將其刪除,避免 Web 瀏覽器的內(nèi)存泄漏。 web 目錄中包含了 JSP 和 JavaScript 示例。ajaxUtil.js 中包含了發(fā)送 Ajax 請求、終止請求以及處理 HTTP 錯誤的實用函數(shù)。該文件還提供了可用于 XML 和 URL 編碼、XML 分析以及 DOM 串行化的 JavaScript 實用程序。ajaxCtrl.jsp 文件充當 Ajax 控制器,接收每一個 Ajax 請求、轉(zhuǎn)發(fā)參數(shù)至數(shù)據(jù)模型,或供給處理,然后返回 Ajax 響應。其余的 Web 文件都是演示如何使用該實用方法的示例。 在客戶端構(gòu)建請求將數(shù)據(jù)發(fā)送至 Web 服務器的最簡單方法是將請求編碼為查詢字符串,該字符串根據(jù)使用的 HTTP 方法,既可附加至 URL,也可包含在請求正文中。如果需要發(fā)送復雜的數(shù)據(jù)結(jié)構(gòu),更好的解決方案是將信息編碼在 XML 文檔中。我將在本部分中介紹這兩種方法。 編碼請求參數(shù)。開發(fā)傳統(tǒng) Web 應用程序時,無需擔心表單數(shù)據(jù)的編碼,因為 Web 瀏覽器會在用戶提交數(shù)據(jù)時自動執(zhí)行該操作。但是,在 Ajax 應用程序中,您必須親自編碼請求參數(shù)。JavaScript 提供了一個非常有用的函數(shù) escape(),該函數(shù)用 %HH(其中 HH 是十六進制代碼)替換任何無法成為 URL 一部分的字符。例如,任何空白字符都用 %20 替換。 示例代碼下載中提供了一個實用函數(shù) buildQueryString(),該函數(shù)可連接檢索自數(shù)組的參數(shù),通過 = 將每個參數(shù)的名稱和值相分離,并將 & 字符置于每個名稱-值對之間: function buildQueryString(params) {var query = "";for (var i = 0; i < params.length; i++) {query += (i > 0 ? "&" : "")+ escape(params[i].name) + "="+ escape(params[i].value); }return query;} 假設(shè)您要編碼以下參數(shù): var someParams = [{ name:"name", value:"John Smith" },{ name:"email", value:"john@company.com" },{ name:"phone", value: "(123) 456 7890" }]; buildQueryString(someParams) 調(diào)用將生成包含以下內(nèi)容的結(jié)果: name=John%20Smith&email=john@company.com&phone=%28123%29%20456%207890 如果希望使用 GET 方法,則必須將查詢附加至 URL 的 ? 字符之后。使用 POST 時,應通過 setRequestHeader() 將 Content-Type 標題設(shè)置為 application/x-www-form-urlencoded,且必須將該查詢字符串傳遞至 XMLHttpRequest 的 send() 方法,這會將該 HTTP 請求發(fā)送至服務器。 創(chuàng)建 XML 文檔。利用字符串通過其屬性和數(shù)據(jù)構(gòu)建元素是用 JavaScript 創(chuàng)建 XML 文檔最簡單的方法。如果采用這種解決方案,則需要一個實用方法來轉(zhuǎn)義 &、<、>、"、以及 ' 字符: function escapeXML(content) {if (content == undefined)return "";if (!content.length || !content.charAt)content = new String(content);var result = "";var length = content.length;for (var i = 0; i < length; i++) {var ch = content.charAt(i);switch (ch) {case '&':result += "&";break;case '<':result += "<";break;case '>':result += ">";break;case '"':result += """;break;case '\'':result += "'";break;default:result += ch; } }return result;} 要使任務更為簡單,還需要一些其他實用程序方法,例如: function attribute(name, value) {return " " + name + "=\"" + escapeXML(value) + "\"";} 以下示例從一個具有以下三個屬性的對象的數(shù)組構(gòu)建一個 XML 文檔:symbol、shares 和 paidPrice: function buildPortfolioDoc(stocks) {var xml = "<portfolio>";for (var i = 0; i < stocks.length; i++) {var stock = stocks[i];xml += "<stock ";xml += attribute("symbol", stock.symbol);xml += attribute("shares", stock.shares);xml += attribute("paidPrice", stock.paidPrice);xml += ""; }xml += "</portfolio>";return xml;} 如果您喜好使用 DOM,則可使用 Web 瀏覽器的 API 分析 XML 和串行化 DOM 樹。通過 IE,您可以用新的 ActiveXObject("Microsoft.XMLDOM") 創(chuàng)建一個空文檔。然后,可以使用 loadXML() 或 load() 方法分別從字符串或 URL 分析該 XML。在使用 IE 的情況下,每個節(jié)點都有一個稱為 xml 的屬性,您可以利用它獲得該節(jié)點及其所有子節(jié)點的 XML 表示。因此,您可以分析 XML 字符串、修改 DOM 樹,然后將該 DOM 串行化回 XML。 Firefox 和 Netscape 瀏覽器允許您使用 document.implementation.createDocument(...) 創(chuàng)建一個空文檔。然后,可以使用 createElement()、createTextNode()、createCDATASection() 等創(chuàng)建 DOM 節(jié)點。Mozilla 瀏覽器還提供了兩個分別名為 DOMParser 和 XMLSerializer 的 API。DOMParser API 包含 parseFromStream() 和 parseFromString() 方法。XMLSerializer 類具有串行化 DOM 樹的相應方法:serializeToStream() 和 serializeToString()。 以下函數(shù)分析一個 XML 字符串并返回 DOM 文檔: function parse(xml) {var dom;try{dom = new ActiveXObject("Microsoft.XMLDOM");dom.async = false;dom.loadXML(xml);} catch (error) {try{var parser = new DOMParser();dom = parser.parseFromString(xml, "text/xml");delete parser;} catch (error2) {if (debug)alert("XML parsing is not supported."); } }return dom;} 第二個函數(shù)串行化一個 DOM 節(jié)點及其所有子節(jié)點,將 XML 作為字符串返回: function serialize(dom) {var xml = dom.xml;if (xml == undefined) {try{var serializer = new XMLSerializer();xml = serializer.serializeToString(dom);delete serializer;} catch (error) {if (debug)alert("DOM serialization is not supported."); } }return xml;} 還可以使用 XMLHttpRequest 作為分析程序或串行化程序。在從服務器接收到對 Ajax 請求的響應后,該響應會自動進行分析??赏ㄟ^ XMLHttpRequest 的 responseText 和 responseXML 屬性分別訪問文本版本和 DOM 樹。此外,在將 DOM 樹傳遞至 send() 方法時自動將其串行化。 發(fā)送請求。在先前的文章中,我介紹了 XMLHttpRequest API 和一個實用函數(shù) sendHttpRequest(),您可以在提供下載的示例中的 ajaxUtil.js 文件中找到。該函數(shù)有四個參數(shù)(HTTP 方法、URL、一個參數(shù)數(shù)組和一個回調(diào)),可創(chuàng)建 XMLHttpRequest 對象,設(shè)置其屬性并調(diào)用 send() 方法。如果提供了回調(diào)參數(shù),則異步發(fā)送請求,并在收到響應后調(diào)用回調(diào)函數(shù)。否則,將同步發(fā)送請求,您可以在 sendHttpRequest() 返回后即刻處理響應。 如您所見,在使用 XMLHttpRequest 時必須進行一些重要選擇
假設(shè)您希望從數(shù)據(jù)饋送了解一些股價信息,且無需用戶干預即可定期刷新信息。在本例中,應異步發(fā)送 HTTP 請求,這是為了在檢索信息時不阻塞用戶界面。請求參數(shù)是一個符號數(shù)組,可在 URL 中進行編碼。由于服務器可能超載,因此您不希望在進行頻繁請求時發(fā)送 XML 文檔。由于您只對最新的股價感興趣,因此應終止任何未完成的先前請求: var ctrlURL = "ajaxCtrl.jsp";var feedRequest = null;function sendInfoRequest(symbols, callback) {if (feedRequest)abortRequest(feedRequest);var params = new Array();for (var i = 0; i < symbols.length; i++)params[i] = {name:"symbol",value:symbols[i] };feedRequest = sendHttpRequest("GET", ctrlURL, params, callback);} 在調(diào)用請求對象的 abort() 方法之前,abortRequest() 函數(shù)(可在 ajaxUtil.js 文件中找到)會將 onreadystatechange 屬性設(shè)置為不執(zhí)行任何操作的回調(diào)。此外,刪除該請求對象以避免內(nèi)存泄漏,這點至關(guān)重要: function abortRequest(request) {function doNothing() { }request.onreadystatechange = doNothing;request.abort();delete feedRequest;} 我們來考慮另一種情況:在傳輸要保存在數(shù)據(jù)庫中的整個用戶數(shù)據(jù)時,應同步發(fā)送請求,因為您可能不希望用戶在保存這些數(shù)據(jù)進行時對其進行修改。在這種情況下,首選 XML 格式,這是因為在文檔中進行對象模型編碼通常要比使用很多字符串參數(shù)更簡單。此外,保存數(shù)據(jù)的請求并不頻繁,服務器可以毫無問題地處理負載??蓪?XML 文檔編碼為參數(shù),這樣您就可以使用 EL 語法 (${param.xml}) 在 JSP 頁面中訪問該文檔了。以下就是發(fā)送在 XML 文檔中編碼的模型數(shù)據(jù)的函數(shù): function sendSaveRequest(xml) {var params = [ { name:"xml", value:xml } ];var saveRequest = sendHttpRequest("POST", ctrlURL, params);if (saveRequest)delete saveRequest;} 如果需要恢復對象模型,則也可同步發(fā)送請求,從服務器檢索數(shù)據(jù)。在這種情況下,服務器應當返回一個 JSON 響應,以便您可利用 eval(loadRequest.responseText) 輕松將其轉(zhuǎn)換為 JavaScript 對象樹: function sendLoadRequest() {var model = null;var loadRequest = sendHttpRequest("GET", ctrlURL);if (loadRequest) {model = eval(loadRequest.responseText);delete loadRequest; }return model;} 以下兩部分介紹了通常在服務器上對 XML 文檔執(zhí)行的操作,以及如何響應 Ajax 請求。 在服務器端處理請求Servlet/JSP 容器分析各個 HTTP 請求并創(chuàng)建一個 ServletRequest 實例,該實例使您可以通過 getParameter() / getParameterValues() 獲得請求參數(shù),或通過 getInputStream() 獲得請求正文。在 JSP 頁面中,也可以使用 EL 語法(${param...} 和 ${paramValues...})獲得這些參數(shù)。請注意,只有在 Ajax 客戶端使用了類似于 buildQueryString() 之類的實用函數(shù),通過 application/x-www-form-urlencoded 格式來編碼數(shù)據(jù)(本文前一部分有述)的情況下,才可通過 getParameter() 或 ${param...} 獲得請求參數(shù)。如果在客戶端上將 XML 文檔或 DOM 樹傳遞至 XMLHttpRequest 的 send() 方法,則必須在服務器端使用 ServletRequest 的 getInputStream() 方法。 數(shù)據(jù)驗證。典型的 Web 應用程序會進行許多數(shù)據(jù)驗證操作。多數(shù)可能的錯誤相當簡單,例如缺少請求參數(shù)、數(shù)字格式錯誤等等。這些錯誤通常是由于用戶忘記輸入表單元素的值或提供了無效值引起的。Web 框架(如 JSF 和 Oracle ADF Faces)非常善于處理這些用戶錯誤。在 Ajax 應用程序中,這些錯誤可以在客戶端使用 JavaScript 來捕獲和處理。例如,您可使用 isNaN(new Number(value)) 驗證數(shù)字值是否無效。 出于安全和可靠性的考慮,應當在服務器端對數(shù)據(jù)進行重新驗證,而不應想當然地認為 XML 請求格式設(shè)置正確。XML 模式是在服務器端驗證復雜請求的有用工具。示例代碼下載中包含了一個名為 XMLUtil 的類,它提供用于加載和使用模式文檔的方法。以下代碼段顯示了如何初始化 SchemaFactory: import javax.xml.*;import javax.xml.validation.*;...protected static SchemaFactory schemaFactory;static {schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);schemaFactory.setErrorHandler(newErrorHandler());}The newErrorHandler() method returns a SAX error handler:import org.xml.sax.*;...public static ErrorHandler newErrorHandler() {return new ErrorHandler() {public void warning(SAXParseException e)throws SAXException {Logger.global.warning(e.getMessage()); }public void error(SAXParseException e)throws SAXException {throw e; }public void fatalError(SAXParseException e)throws SAXException {throw e; } };} 可以使用 getResourceAsStream() 查找并加載某個目錄中的 XSD 文件或 CLASSPATH 中指定的 JAR: public static InputStream getResourceAsStream(String name)throws IOException {InputStream in = XMLUtil.class.getResourceAsStream(name);if (in == null)throw new FileNotFoundException(name);return in;} 然后,使用 SchemaFactory 實例通過 newSchema() 方法獲取 Schema 對象: import javax.xml.validation.*;...public static Schema newSchema(String name)throws IOException, SAXException {Schema schema;InputStream in = getResourceAsStream(name);try{schema = schemaFactory.newSchema(new StreamSource(in));}finally{in.close(); }return schema;} 您還可以使用以下方法創(chuàng)建 Oracle XMLSchema 對象: import oracle.xml.parser.schema.XMLSchema;import oracle.xml.parser.schema.XSDBuilder;...public static XMLSchema newOracleSchema(String name)throws IOException, SAXException {XMLSchema schema;InputStream in = getResourceAsStream(name);try{XSDBuilder builder = new XSDBuilder();schema = builder.build(new InputSource(in));} catch (Exception e){throw new SAXException(e);}finally{in.close(); }return schema;} 接下來,您需要創(chuàng)建一個 DocumentBuilderFactory。如果在 CLASSPATH 中找到的是 JAXP 1.1 實現(xiàn),則由 JAXP 1.2 定義的 setSchema() 方法可能會拋出 UnsupportedOperationException,此時需要將 JAXP 1.1 實現(xiàn)替換為 Java SE 5.0 的 JAXP 1.2 實現(xiàn)。在這種情況下,您仍可使用 newOracleSchema() 創(chuàng)建模式對象,并通過 setAttribute()方法對其進行設(shè)置: import javax.xml.parsers.*;import oracle.xml.jaxp.JXDocumentBuilderFactory;...public static DocumentBuilderFactory newParserFactory(String schemaName) throws IOException, SAXException {DocumentBuilderFactory parserFactory= DocumentBuilderFactory.newInstance();try{parserFactory.setSchema(newSchema(schemaName));} catch (UnsupportedOperationException e) {if (parserFactory instanceof JXDocumentBuilderFactory) {parserFactory.setAttribute(JXDocumentBuilderFactory.SCHEMA_OBJECT,newOracleSchema(schemaName)); } }return parserFactory;} 然后,創(chuàng)建一個 DocumentBuilder 對象,并使用該對象驗證和分析 XML 文檔: import javax.xml.parsers.*;...public static DocumentBuilder newParser(DocumentBuilderFactory parserFactory)throws ParserConfigurationException {DocumentBuilder parser = parserFactory.newDocumentBuilder();parser.setErrorHandler(newErrorHandler());return parser;}; 假設(shè)您要根據(jù) portfolio.xsd 模式示例驗證 XML 文檔: <xsd:schema xmlns:xsd="http://www./2001/XMLSchema"><xsd:element name="portfolio" type="portfolioType"<xsd:complexType name="portfolioType"><xsd:sequence><xsd:element name="stock"minOccurs="0" maxOccurs="unbounded"><xsd:complexType><xsd:attribute name="symbol"type="xsd:string" use="required"/><xsd:attribute name="shares"type="xsd:positiveInteger" use="required"/><xsd:attribute name="paidPrice"type="xsd:decimal" use="required"/></xsd:complexType></xsd:element></xsd:sequence></xsd:complexType></xsd:schema> DataModel 類的 parsePortfolioDoc() 方法使用 XMLUtil 驗證和分析 xml 參數(shù),并返回一個 DOM 文檔: private static final String SCHEMA_NAME= "/ajaxapp/model/portfolio.xsd";private static DocumentBuilderFactory parserFactory; private static Document parsePortfolioDoc(String xml)throws IOException, SAXException,ParserConfigurationException {synchronized (DataModel.class) {if (parserFactory == null)parserFactory = XMLUtil.newParserFactory(SCHEMA_NAME); }DocumentBuilder parser = XMLUtil.newParser(parserFactory);InputSource in = new InputSource(new StringReader(xml));return parser.parse(in);} 現(xiàn)在,您擁有了一個 DOM 樹,接下來要獲取形成 DOM 節(jié)點所需的數(shù)據(jù)。 提取所需信息。您可以使用 DOM API 或查詢語言(如 XQuery 或 XPath)來瀏覽 DOM 樹。Java 為 XPath 提供了標準的 API,后面會用到。XMLUtil 類創(chuàng)建一個具有 newXPath() 方法的 XPathFactory: import javax.xml.xpath.*;...protected static XPathFactory xpathFactory;static {xpathFactory = XPathFactory.newInstance();} public static XPath newXPath() {return xpathFactory.newXPath();} 以下方法在給定的上下文中求解 XPath 表達式,返回結(jié)果值: import javax.xml.xpath.*;import org.w3c.dom.*;...public static String evalToString(String expression,Object context) throws XPathExpressionException {return (String) newXPath().evaluate(expression, context,XPathConstants.STRING);} public static boolean evalToBoolean(String expression,Object context) throws XPathExpressionException {return ((Boolean) newXPath().evaluate(expression, context,XPathConstants.BOOLEAN)).booleanValue();} public static double evalToNumber(String expression,Object context) throws XPathExpressionException {return ((Double) newXPath().evaluate(expression, context,XPathConstants.NUMBER)).doubleValue();} public static Node evalToNode(String expression,Object context) throws XPathExpressionException {return (Node) newXPath().evaluate(expression, context,XPathConstants.NODE);} public static NodeList evalToNodeList(String expression,Object context) throws XPathExpressionException {return (NodeList) newXPath().evaluate(expression, context,XPathConstants.NODESET);} DataModel 的 setData() 方法使用 XPath 求解方法從組合 XML 文檔提取信息: public synchronized void setData(String xml)throws IOException, SAXException,ParserConfigurationException,XPathExpressionException {try{ArrayList 一旦服務器端的數(shù)據(jù)模型中具備了數(shù)據(jù),就可根據(jù)應用程序的要求對其進行處理了。然后,您必須響應 Ajax 請求。 在服務器端生成響應將 HTML 作為 Ajax 請求的響應而返回是一種最簡單的解決方案,這是因為您可以使用 JSP 語法構(gòu)建標記,而 Ajax 客戶端只需使用 <div> 或 <span> 元素的 innerHTML 屬性在頁面某處插入 HTML。但是,向 Ajax 客戶端返回不帶任何表示標記的數(shù)據(jù)則更為有效。您可以使用 XML 格式或 JSON。 生成 XML 響應。Java EE 提供了很多創(chuàng)建 XML 文檔的選項:可通過 JSP 生成、通過 JAXB 從對象樹創(chuàng)建、或利用 javax.xml.transform 生成。以下示例中的轉(zhuǎn)換程序?qū)⒋谢粋€ DOM 樹: import javax.xml.transform.*;import javax.xml.transform.dom.*;import javax.xml.transform.stream.*;...public static TransformerFactory serializerFctory;static {serializerFctory = TransformerFactory.newInstance();} public static void serialize(Node node, OutputStream out)throws TransformerException {Transformer serializer = serializerFctory.newTransformer();Properties serializerProps = new Properties();serializerProps.put(OutputKeys.METHOD, "xml");serializer.setOutputProperties(serializerProps);Source source = new DOMSource(node);Result result = new StreamResult(out);serializer.transform(source, result);} 有這么多可在服務器端生成 XML 的標準選項和開發(fā)源框架,您唯一所要做的就是選擇一個適合你的選項。但是,在客戶端上,由于只能使用 DOM 來分析 XML,因此情況非常不同。某些瀏覽器還支持 XPath 和 XSLT。 在先前的 Ajax 文章中,您學習了如何通過 JSP 生成 XML,然后在客戶端上利用 JavaScript 和 DOM 對其進行分析。另一個解決方案是使用 JSON 而非 XML 作為響應 Ajax 請求的數(shù)據(jù)格式。如前所述,JSON 字符串可通過 eval() 函數(shù)轉(zhuǎn)化為 JavaScript 對象樹。較之利用 JavaScript 從 DOM 樹提取信息而言,這更為簡單些。您所需的就是一個在服務器端生成 JSON 的良好實用類。 JSON 編碼。JSONEncoder 類提供了編碼文字、對象和數(shù)組的方法。結(jié)果存儲在 java.lang.StringBuilder 中: package ajaxapp.util;public class JSONEncoder {private StringBuilder buf; public JSONEncoder() {buf = new StringBuilder(); } ...} character() 方法編碼單一字符: public void character(char ch) {switch (ch) {case '\'':case '\"':case '\\':buf.append('\\');buf.append(ch);break;case '\t':buf.append('\\');buf.append('t');break;case '\r':buf.append('\\');buf.append('r');break;case '\n':buf.append('\\');buf.append('n');break;default:if (ch >= 32 && ch < 128)buf.append(ch);else{buf.append('\\');buf.append('u');for (int j = 12; j >= 0; j-=4) {int k = (((int) ch) >> j) & 0x0f;int c = k < 10 ?'0' + k :'a' + k - 10;buf.append((char) c); } } }} string() 方法編碼整個字符串: public void string(String str) {int length = str.length();for (int i = 0; i < length; i++)character(str.charAt(i));} literal() 方法編碼 JavaScript 文字: public void literal(Object value) {if (value instanceof String) {buf.append('"');string((String) value);buf.append('"');} else if (value instanceof Character) {buf.append('\'');character(((Character) value).charValue());buf.append('\'');} elsebuf.append(value.toString());} comma() 方法附加一個逗號字符: private void comma() {buf.append(',');} deleteLastComma() 方法將移除緩沖區(qū)末尾最后一個逗號字符(如果有的話): private void deleteLastComma() {if (buf.length() > 0)if (buf.charAt(buf.length()-1) == ',')buf.deleteCharAt(buf.length()-1);} startObject() 方法附加一個 { 字符,用于表示一個 JavaScript 對象的開始: public void startObject() {buf.append('{');} property() 方法編碼 JavaScript 屬性: public void property(String name, Object value) {buf.append(name);buf.append(':');literal(value);comma();} endObject() 方法附加一個 } 字符,用于表示一個 JavaScript 對象的結(jié)束: public void endObject() {deleteLastComma();buf.append('}');comma();} startArray() 方法附加一個 [ 字符,用于表示一個 JavaScript 數(shù)組的開始: public void startArray() {buf.append('[');} element() 方法編碼 JavaScript 數(shù)組的元素: public void element(Object value) {literal(value);comma();} endArray() 方法附加一個 ] 字符,用于表示一個 JavaScript 數(shù)組的結(jié)束: public void endArray() {deleteLastComma();buf.append(']');comma();} toString() 方法返回 JSON 字符串: public String toString() {deleteLastComma();return buf.toString();} clear() 方法清空緩沖區(qū): public void clear() {buf.setLength(0);} DataModel 使用 JSONEncoder 類來編碼其維護的數(shù)據(jù): public synchronized String getData() {JSONEncoder json = new JSONEncoder();json.startArray();for (int i = 0; i < stockList.size(); i++) {StockBean stock = stockList.get(i);json.startObject();json.property("symbol", stock.getSymbol());json.property("shares", stock.getShares());json.property("paidPrice", stock.getPaidPrice());json.endObject(); }json.endArray();return json.toString();} 如果提供了 xml 請求參數(shù),則 ajaxCtrl.jsp 頁面將設(shè)置模型的數(shù)據(jù)。否則,該頁面會使用 ${dataModel.data} EL 表達式輸出 getData() 返回的 JSON 字符串: <%@ taglib prefix="c" uri="http://java./jsp/jstl/core" %>...<jsp:useBean id="dataModel" scope="session"class="ajaxapp.model.DataModel" /><c:choose> ...<c:when test="${!empty param.xml}"><c:set target="${dataModel}" property="data" value="${param.xml}" /></c:when><c:otherwise>${dataModel.data}</c:otherwise></c:choose> 這個作業(yè)并未完成,因為 Ajax 客戶端必須處理 JSON 數(shù)據(jù)。 在客戶端處理響應在典型的 Web 應用程序中,您使用 JSP、Web 框架和標記庫在服務器端生成內(nèi)容。Ajax 應用程序非常適合這種情況,因為 Web 框架(如 JavaServer Faces 和 Oracle ADF Faces)在構(gòu)建 Ajax 應用程序時非常有用。然而,Ajax 和非 Ajax 應用程序之間仍然存在著明顯不同。使用 Ajax 時,您必須在客戶端處理數(shù)據(jù),并用 JavaScript 動態(tài)生成內(nèi)容來向用戶提供數(shù)據(jù)。 如果使用 JSON 格式進行數(shù)據(jù)轉(zhuǎn)換,則使用 JavaScript 提供的 eval() 函數(shù)將文本轉(zhuǎn)換為對象樹非常容易。如果喜歡使用 XML,則還需要執(zhí)行許多其他操作,但這種格式也有其自身的優(yōu)勢。例如,許多類型的客戶端可以使用 XML,而 JSON 只在 JavaScript 環(huán)境中易于分析。此外,在使用 XML 的情況下,可以更快地發(fā)現(xiàn)并修正錯誤,從而縮短了調(diào)試時間。 使用 JavaScript 訪問 DOM 樹。JavaScript 的 DOM API 非常類似于 Java 的 org.w3c.dom 程序包。主要的區(qū)別在于對屬性的訪問。在 JavaScript 中可直接訪問屬性,而 Java 將屬性視為私有,您需要通過 get 和 set 方法進行訪問。例如,您可通過 dom.documentElement 獲得文檔的根元素。 DOM 是一種低級的 API,利用它可以訪問已分析文檔的結(jié)構(gòu)。例如,在大多數(shù)情況下您都想忽略注釋,并可能不愿意擁有相鄰的文本節(jié)點。來看下面這個簡單示例: var xml = "<element>da<!--comment-->ta&"+ "<![CDATA[cdata</element>"; 您可以使用先前介紹的實用函數(shù)分析上述 XML 字符串: var dom = parse(xml); 您可以在 ajaxUtil.js 中找到 parse() 函數(shù)的代碼;本例中,該函數(shù)返回一個 DOM 樹,其根元素包含一個文本節(jié)點,其后是一條注釋、另一個文本節(jié)點和一個字符數(shù)據(jù)節(jié)點。如果希望包含的文本不帶注釋,則必須迭代元素的子元素,連接文本和字符數(shù)據(jù)節(jié)點的值(其類型分別為 3 和 4): var element = dom.documentElement;var childNodes = element.childNodes;var text = "";for (var i = 0; i < childNodes.length; i++)if (childNodes[i].nodeValue) {var type = childNodes[i].nodeType;if (type == 3 || type == 4)text += childNodes[i].nodeValue; } 使用 DOM 時,應當構(gòu)建一個小的實用函數(shù)集,以避免處理上述這些低級細節(jié)。 使用 JavaScript 生成動態(tài)內(nèi)容。Web 瀏覽器使您可以通過文檔對象訪問 Web 頁面的 DOM 結(jié)構(gòu)。例如,您可以利用 document.getElementById(...) 非常輕松地找到一個元素。還可以創(chuàng)建可插入現(xiàn)有文檔的新元素和文本節(jié)點。然而,如下所示,通過連接字符串構(gòu)建 HTML 要更為簡單: function updateInfo(request) {var shares = eval(request.responseText);var table = "<table border=1 cellpadding=5>";table += "<tr>";table += "<th>Symbol</th>";table += "<th>Trend</th>";table += "<th>Last Price</th>";table += "</tr>";for (var i = 0; i < shares.length; i++) {var share = shares[i];var symbol = escapeXML(share.symbol)var trend = share.trend > 0 ? "+" : "-";var lastPrice = new Number(share.lastPrice).toFixed(2);table += "<tr>";table += "<td>" + symbol + "</td>";table += "<td>" + trend + "</td>";table += "<td>" + lastPrice + "</td>";table += "</tr>"; }table += "</table>";document.getElementById("table").innerHTML = table;} 通過設(shè)置由 getElementById() 返回的對象的 innerHTML 屬性,可將生成的 HTML 插入空 <div> 元素中,例如: <div id="table"></div> 本文的示例將 updateInfo() 函數(shù)用作回調(diào)來處理對 Ajax 請求的響應,該請求是通過 ajaxLogic.js 文件中的 sendInfoRequest 發(fā)送到服務器的。如果希望每 5 秒更新一次信息,可使用 JavaScript 的 setInterval() 函數(shù): var symbols = [ ... ];setInterval("sendInfoRequest(symbols, updateInfo)", 5000); 一個名為 DataFeed 的類模擬服務器端的饋送。ajaxCtrl.jsp 頁面調(diào)用該饋送的 getData() 方法,將響應作為 JSON 字符串返回。在客戶端,updateInfo() 函數(shù)利用 eval(request.responseText) 對該 JSON 字符串進行分析,如上述代碼示例中所示。 結(jié)論在本文中,首先介紹了如何使用 URL 編碼和 XML 格式從 Ajax 客戶端向服務器傳輸數(shù)據(jù)。然后,闡釋了如何通過 XML 模式驗證數(shù)據(jù)、利用 XPath 提取所需信息以及在服務器端生成 XML 或 JSON 響應。最后,介紹了如何利用 JavaScript 處理 XML 和 JSON 響應,以及如何生成用于更新瀏覽器中 Web 頁面的動態(tài)內(nèi)容。 |
|