AJax 核心 API(即所謂的 XMLHttpRequest)的唯一用途就是發送 HTTP 請求,在 Web 浏覽器與服務器之間進行數據交換。Web 頁面中運行的 JavaScript 代碼,可以使用 XMLHttpRequest 將該請求參數提交至服務器端腳本,例如 Servlet 或 JSP 頁面。調用的 Servlet/JSP 將發回一個響應,其中包含了一般用於不需刷新整個頁面即可更新用戶查看內容的數據。此種方法在性能和可用性方面均體現出了獨有的優勢,因為這將降低網絡通信量,而且 Web UI 的使用幾乎與桌面 GUI 一樣。
但是,開發這種用戶界面並不簡單,因為您必須在客戶端上使用 Javascript、在服務器端上使用 Java(或等效語言)實施數據交換、驗證以及處理。然而,在許多情況下,考慮到將會由此獲得的益處,付出額外精力構建一個基於 AJax 的界面是值得的。
在本文中,我將介紹一種用於在 Ajax 客戶端和服務器之間傳輸數據的主要方法,並比較傳統 Web 應用程序模型與該 AJax 模型的不同點。此外,文中還將探討在服務器端與客戶端處理數據的技巧。
首先,您將了解如何在客戶端使用 Javascript 編碼請求對象的參數。您可以使用所謂的 URL 編碼(Web 浏覽器使用的默認編碼),或可將請求參數包含在 XML 文檔中。服務器將處理該請求,並返回一個其數據也必須進行編碼的響應。本文將探討 JavaScript Object Notation (JSON) 和 XML,這些都是主要的響應數據格式選項。
本文的大部分內容將主要介紹 AJax 應用程序中通常使用的與 XML 相關的 API。在客戶端,XML API 的作用雖非常有限,但已夠用。在多數情況下,利用 XMLHttpRequest 即可完成所有必需操作。此外,還可使用 Javascript 在 Web 浏覽器中分析 XML 文檔並串行化 DOM 樹。在服務器端,可用於處理 XML 文檔的 API 和框架有很多種。本文將介紹如何使用針對 XML 的標准 Java API 來實施基本任務,該 API 支持 XML 模式、XPath、DOM 以及許多其他標准。
通過本文,您可以了解到在 Ajax 應用程序中實現數據交換所用的最佳技巧和最新的 API。其中涉及的示例代碼分別位於以下三個程序包中:util、model 和 feed。util 程序包中的類提供了用於 XML 分析、基於模式的驗證、基於 XPath 的查詢、DOM 串行化以及 JSON 編碼的方法。model 程序包包含的示例數據模型可用於從 XML 文檔進行初始化,然後再轉換至 JSON 格式。model 目錄中還有一個 Schema 示例,可用於 XML 驗證。feed 程序包中的類可用於模擬數據饋送,其通過 Ajax 每 5 秒檢索一次來獲得信息,以刷新 Web 頁面。本文闡釋了如何通過終止未完成的 AJax 請求並在使用完 XMLHttpRequest 對象後將其刪除,避免 Web 浏覽器的內存洩漏。
web 目錄中包含了 JSP 和 Javascript 示例。ajaxUtil.JS 中包含了發送 AJax 請求、終止請求以及處理 HTTP 錯誤的實用函數。該文件還提供了可用於 XML 和 URL 編碼、XML 分析以及 DOM 串行化的 JavaScript 實用程序。AJaxCtrl.JSP 文件充當 Ajax 控制器,接收每一個 Ajax 請求、轉發參數至數據模型,或供給處理,然後返回 AJax 響應。其余的 Web 文件都是演示如何使用該實用方法的示例。
在客戶端構建請求
將數據發送至 Web 服務器的最簡單方法是將請求編碼為查詢字符串,該字符串根據使用的 HTTP 方法,既可附加至 URL,也可包含在請求正文中。如果需要發送復雜的數據結構,更好的解決方案是將信息編碼在 XML 文檔中。我將在本部分中介紹這兩種方法。
編碼請求參數。開發傳統 Web 應用程序時,無需擔心表單數據的編碼,因為 Web 浏覽器會在用戶提交數據時自動執行該操作。但是,在 AJax 應用程序中,您必須親自編碼請求參數。JavaScript 提供了一個非常有用的函數 escape(),該函數用 %HH(其中 HH 是十六進制代碼)替換任何無法成為 URL 一部分的字符。例如,任何空白字符都用 %20 替換。
示例代碼下載中提供了一個實用函數 buildQueryString(),該函數可連接檢索自數組的參數,通過 = 將每個參數的名稱和值相分離,並將 & 字符置於每個名稱-值對之間:
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;
}
假設您要編碼以下參數:
var someParams = [
{ name:"name", value:"John Smith" },
{ name:"email", value:"john@company.com" },
{ name:"phone", value: "(123) 456 7890" }
];
buildQueryString(someParams) 調用將生成包含以下內容的結果:
name=John%20Smith&email=john@company.com&phone=%28123%29%20456%207890
如果希望使用 GET 方法,則必須將查詢附加至 URL 的 ? 字符之後。使用 POST 時,應通過 setRequestHeader() 將 Content-Type 標題設置為 application/x-www-form-urlencoded,且必須將該查詢字符串傳遞至 XMLHttpRequest 的 send() 方法,這會將該 HTTP 請求發送至服務器。
創建 XML 文檔。利用字符串通過其屬性和數據構建元素是用 JavaScript 創建 XML 文檔最簡單的方法。如果采用這種解決方案,則需要一個實用方法來轉義 &、< 、>、"、以及 字符:
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) + "\"";
}
以下示例從一個具有以下三個屬性的對象的數組構建一個 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") 創建一個空文檔。然後,可以使用 loadXML() 或 load() 方法分別從字符串或 URL 分析該 XML。在使用 IE 的情況下,每個節點都有一個稱為 xml 的屬性,您可以利用它獲得該節點及其所有子節點的 XML 表示。因此,您可以分析 XML 字符串、修改 DOM 樹,然後將該 DOM 串行化回 XML。
Firefox 和 Netscape 浏覽器允許您使用 document.implementation.createDocument(...) 創建一個空文檔。然後,可以使用 createElement()、createTextNode()、createCDATASection() 等創建 DOM 節點。Mozilla 浏覽器還提供了兩個分別名為 DOMParser 和 XMLSerializer 的 API。DOMParser API 包含 parseFromStream() 和 parseFromString() 方法。XMLSerializer 類具有串行化 DOM 樹的相應方法:serializeToStream() 和 serializeToString()。
以下函數分析一個 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;
}
第二個函數串行化一個 DOM 節點及其所有子節點,將 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() 方法時自動將其串行化。
發送請求。在先前的文章中,我介紹了 XMLHttpRequest API 和一個實用函數 sendHttpRequest(),您可以在提供下載的示例中的 AJaxUtil.JS 文件中找到。該函數有四個參數(HTTP 方法、URL、一個參數數組和一個回調),可創建 XMLHttpRequest 對象,設置其屬性並調用 send() 方法。如果提供了回調參數,則異步發送請求,並在收到響應後調用回調函數。否則,將同步發送請求,您可以在 sendHttpRequest() 返回後即刻處理響應。
如您所見,在使用 XMLHttpRequest 時必須進行一些重要選擇
將要使用的 HTTP 方法(GET 或 POST)
用於編碼請求參數的格式(本文前面已探討了 XML 和 URL 編碼)
是進行同步(等待響應)調用還是異步(使用回調)調用
響應的格式,如 XML、XHTML、Html 或 JavaScript Object Notation (JSON)(本文稍後將對此進行探討)。假設您希望從數據饋送了解一些股價信息,且無需用戶干預即可定期刷新信息。在本例中,應異步發送 HTTP 請求,這是為了在檢索信息時不阻塞用戶界面。請求參數是一個符號數組,可在 URL 中進行編碼。由於服務器可能超載,因此您不希望在進行頻繁請求時發送 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);
}
在調用請求對象的 abort() 方法之前,abortRequest() 函數(可在 AJaxUtil.JS 文件中找到)會將 onreadystatechange 屬性設置為不執行任何操作的回調。此外,刪除該請求對象以避免內存洩漏,這點至關重要:
function abortRequest(request) {
function doNothing() {
}
request.onreadystatechange = doNothing;
request.abort();
delete feedRequest;
}
我們來考慮另一種情況:在傳輸要保存在數據庫中的整個用戶數據時,應同步發送請求,因為您可能不希望用戶在保存這些數據進行時對其進行修改。在這種情況下,首選 XML 格式,這是因為在文檔中進行對象模型編碼通常要比使用很多字符串參數更簡單。此外,保存數據的請求並不頻繁,服務器可以毫無問題地處理負載。可將 XML 文檔編碼為參數,這樣您就可以使用 EL 語法 (${param.XML}) 在 JSP 頁面中訪問該文檔了。以下就是發送在 XML 文檔中編碼的模型數據的函數:
function sendSaveRequest(XML) {
var params = [ { name:"xml", value:XML } ];
var saveRequest = sendHttpRequest("POST", ctrlURL, params);
if (saveRequest)
delete saveRequest;
}
如果需要恢復對象模型,則也可同步發送請求,從服務器檢索數據。在這種情況下,服務器應當返回一個 JSON 響應,以便您可利用 eval(loadRequest.responseText) 輕松將其轉換為 JavaScript 對象樹:
function sendLoadRequest() {
var model = null;
var loadRequest = sendHttpRequest("GET", ctrlURL);
if (loadRequest) {
model = eval(loadRequest.responseText);
delete loadRequest;
}
return model;
}
以下兩部分介紹了通常在服務器上對 XML 文檔執行的操作,以及如何響應 AJax 請求。
在服務器端處理請求
Servlet/JSP 容器分析各個 HTTP 請求並創建一個 ServletRequest 實例,該實例使您可以通過 getParameter() / getParameterValues() 獲得請求參數,或通過 getInputStream() 獲得請求正文。在 JSP 頁面中,也可以使用 EL 語法(${param...} 和 ${paramValues...})獲得這些參數。請注意,只有在 AJax 客戶端使用了類似於 buildQueryString() 之類的實用函數,通過 application/x-www-form-urlencoded 格式來編碼數據(本文前一部分有述)的情況下,才可通過 getParameter() 或 ${param...} 獲得請求參數。如果在客戶端上將 XML 文檔或 DOM 樹傳遞至 XMLHttpRequest 的 send() 方法,則必須在服務器端使用 ServletRequest 的 getInputStream() 方法。
數據驗證。典型的 Web 應用程序會進行許多數據驗證操作。多數可能的錯誤相當簡單,例如缺少請求參數、數字格式錯誤等等。這些錯誤通常是由於用戶忘記輸入表單元素的值或提供了無效值引起的。Web 框架(如 JSF 和 Oracle ADF Faces)非常善於處理這些用戶錯誤。在 AJax 應用程序中,這些錯誤可以在客戶端使用 JavaScript 來捕獲和處理。例如,您可使用 isNaN(new Number(value)) 驗證數字值是否無效。
出於安全和可靠性的考慮,應當在服務器端對數據進行重新驗證,而不應想當然地認為 XML 請求格式設置正確。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;
}
您還可以使用以下方法創建 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;
}
接下來,您需要創建一個 DocumentBuilderFactory。如果在 CLASSPATH 中找到的是 JAXP 1.1 實現,則由 JAXP 1.2 定義的 setSchema() 方法可能會拋出 UnsupportedOperationException,此時需要將 JAXP 1.1 實現替換為 Java SE 5.0 的 JAXP 1.2 實現。在這種情況下,您仍可使用 newOracleSchema() 創建模式對象,並通過 setAttribute()方法對其進行設置:
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;
}
然後,創建一個 DocumentBuilder 對象,並使用該對象驗證和分析 XML 文檔:
import Javax.XML.parsers.*;
...
public static DocumentBuilder newParser(
DocumentBuilderFactory parserFactory)
throws ParserConfigurationException {
DocumentBuilder parser = parserFactory.newDocumentBuilder();
parser.setErrorHandler(newErrorHandler());
return parser;
};
假設您要根據 portfolio.xsd 模式示例驗證 XML 文檔:
< xsd:schema XMLns:xsd="http://www.w3.org/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 參數,並返回一個 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);
}
現在,您擁有了一個 DOM 樹,接下來要獲取形成 DOM 節點所需的數據。
提取所需信息。您可以使用 DOM API 或查詢語言(如 XQuery 或 XPath)來浏覽 DOM 樹。Java 為 XPath 提供了標准的 API,後面會用到。XMLUtil 類創建一個具有 newXPath() 方法的 XPathFactory:
import Javax.XML.xpath.*;
...
protected static XPathFactory xpathFactory;
static {
xpathFactory = XPathFactory.newInstance();
}
public static XPath newXPath() {
return xpathFactory.newXPath();
}
以下方法在給定的上下文中求解 XPath 表達式,返回結果值:
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 stockList
= new ArrayList();
Document doc = parsePortfolioDoc(XML);
NodeList nodeList = XMLUtil.evalToNodeList(
"/portfolio/stock", doc);
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
StockBean stock = new StockBean();
stock.setSymbol(
XMLUtil.evalToString("@symbol", node));
stock.setShares(
(int) XMLUtil.evalToNumber("@shares", node));
stock.setPaidPrice(
XMLUtil.evalToNumber("@paidPrice", node));
stockList.add(stock);
}
this.stockList = stockList;
} catch (Exception e){
Logger.global.logp(Level.SEVERE, "DataModel", "setData",
e.getMessage(), e);
}
}
一旦服務器端的數據模型中具備了數據,就可根據應用程序的要求對其進行處理了。然後,您必須響應 AJax 請求。
在服務器端生成響應
將 HTML 作為 AJax 請求的響應而返回是一種最簡單的解決方案,這是因為您可以使用 JSP 語法構建標記,而 AJax 客戶端只需使用 < div> 或 < span> 元素的 innerHTML 屬性在頁面某處插入 Html。但是,向 AJax 客戶端返回不帶任何表示標記的數據則更為有效。您可以使用 XML 格式或 JSON。
生成 XML 響應。Java EE 提供了很多創建 XML 文檔的選項:可通過 JSP 生成、通過 JAXB 從對象樹創建、或利用 Javax.XML.transform 生成。以下示例中的轉換程序將串行化一個 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 的標准選項和開發源框架,您唯一所要做的就是選擇一個適合你的選項。但是,在客戶端上,由於只能使用 DOM 來分析 XML,因此情況非常不同。某些浏覽器還支持 XPath 和 XSLT。
在先前的 AJax 文章中,您學習了如何通過 JSP 生成 XML,然後在客戶端上利用 Javascript 和 DOM 對其進行分析。另一個解決方案是使用 JSON 而非 XML 作為響應 AJax 請求的數據格式。如前所述,JSON 字符串可通過 eval() 函數轉化為 Javascript 對象樹。較之利用 JavaScript 從 DOM 樹提取信息而言,這更為簡單些。您所需的就是一個在服務器端生成 JSON 的良好實用類。
JSON 編碼。JSONEncoder 類提供了編碼文字、對象和數組的方法。結果存儲在 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 \ :
buf.append(\\\);
buf.append( );
break;
case \
:
buf.append(\\\);
buf.append(
);
break;
case \
:
buf.append(\\\);
buf.append(
);
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 ? + 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(\\);
} else
buf.append(value.toString());
}
comma() 方法附加一個逗號字符:
private void comma() {
buf.append(,);
}
deleteLastComma() 方法將移除緩沖區末尾最後一個逗號字符(如果有的話):
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 對象的結束:
public void endObject() {
deleteLastComma();
buf.append(});
comma();
}
startArray() 方法附加一個 [ 字符,用於表示一個 JavaScript 數組的開始:
public void startArray() {
buf.append([);
}
element() 方法編碼 JavaScript 數組的元素:
public void element(Object value) {
literal(value);
comma();
}
endArray() 方法附加一個 ] 字符,用於表示一個 JavaScript 數組的結束:
public void endArray() {
deleteLastComma();
buf.append(]);
comma();
}
toString() 方法返回 JSON 字符串:
public String toString() {
deleteLastComma();
return buf.toString();
}
clear() 方法清空緩沖區:
public void clear() {
buf.setLength(0);
}
DataModel 使用 JSONEncoder 類來編碼其維護的數據:
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 請求參數,則 AJaxCtrl.JSP 頁面將設置模型的數據。否則,該頁面會使用 ${dataModel.data} EL 表達式輸出 getData() 返回的 JSON 字符串:
< %@ taglib prefix="c" uri="http://Java.sun.com/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>
這個作業並未完成,因為 AJax 客戶端必須處理 JSON 數據。
在客戶端處理響應
在典型的 Web 應用程序中,您使用 JSP、Web 框架和標記庫在服務器端生成內容。Ajax 應用程序非常適合這種情況,因為 Web 框架(如 JavaServer Faces 和 Oracle ADF Faces)在構建 Ajax 應用程序時非常有用。然而,Ajax 和非 Ajax 應用程序之間仍然存在著明顯不同。使用 AJax 時,您必須在客戶端處理數據,並用 JavaScript 動態生成內容來向用戶提供數據。
如果使用 JSON 格式進行數據轉換,則使用 Javascript 提供的 eval() 函數將文本轉換為對象樹非常容易。如果喜歡使用 XML,則還需要執行許多其他操作,但這種格式也有其自身的優勢。例如,許多類型的客戶端可以使用 XML,而 JSON 只在 JavaScript 環境中易於分析。此外,在使用 XML 的情況下,可以更快地發現並修正錯誤,從而縮短了調試時間。
使用 JavaScript 訪問 DOM 樹。JavaScript 的 DOM API 非常類似於 Java 的 org.w3c.dom 程序包。主要的區別在於對屬性的訪問。在 Javascript 中可直接訪問屬性,而 Java 將屬性視為私有,您需要通過 get 和 set 方法進行訪問。例如,您可通過 dom.documentElement 獲得文檔的根元素。
DOM 是一種低級的 API,利用它可以訪問已分析文檔的結構。例如,在大多數情況下您都想忽略注釋,並可能不願意擁有相鄰的文本節點。來看下面這個簡單示例:
var XML = "< element>da< !--comment-->ta&"
+ "< ![CDATA[cdata< /element>";
您可以使用先前介紹的實用函數分析上述 XML 字符串:
var dom = parse(XML);
您可以在 AJaxUtil.JS 中找到 parse() 函數的代碼;本例中,該函數返回一個 DOM 樹,其根元素包含一個文本節點,其後是一條注釋、另一個文本節點和一個字符數據節點。如果希望包含的文本不帶注釋,則必須迭代元素的子元素,連接文本和字符數據節點的值(其類型分別為 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 時,應當構建一個小的實用函數集,以避免處理上述這些低級細節。
使用 JavaScript 生成動態內容。Web 浏覽器使您可以通過文檔對象訪問 Web 頁面的 DOM 結構。例如,您可以利用 document.getElementById(...) 非常輕松地找到一個元素。還可以創建可插入現有文檔的新元素和文本節點。然而,如下所示,通過連接字符串構建 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;
}
通過設置由 getElementById() 返回的對象的 innerHTML 屬性,可將生成的 Html 插入空
元素中,例如:
< div id="table">
< /div>
本文的示例將 updateInfo() 函數用作回調來處理對 Ajax 請求的響應,該請求是通過 AJaxLogic.JS 文件中的 sendInfoRequest 發送到服務器的。如果希望每 5 秒更新一次信息,可使用 JavaScript 的 setInterval() 函數:
var symbols = [ ... ];
setInterval("sendInfoRequest(symbols, updateInfo)", 5000);
一個名為 DataFeed 的類模擬服務器端的饋送。AJaxCtrl.JSP 頁面調用該饋送的 getData() 方法,將響應作為 JSON 字符串返回。在客戶端,updateInfo() 函數利用 eval(request.responseText) 對該 JSON 字符串進行分析,如上述代碼示例中所示。