AJAX 介紹
AJAX 基礎(chǔ)
AJAX 支持動態(tài)、異步的 Web 體驗,不需要頁面刷新。它集成了以下技術(shù):
- XHTML 和 CSS 提供了基于標(biāo)準(zhǔn)的表示。
- 文檔對象模型(DOM)提供了動態(tài)顯示和交互。
- XML 和 XSLT 提供了數(shù)據(jù)交換和操縱。
XMLHttpRequest
提供了異步數(shù)據(jù)檢索。 - javascript 把每一樣?xùn)|西綁定在一起。
AJAX 技術(shù)的核心是 javascript 對象:XMLHttpRequest
。這個對象是通過瀏覽器實現(xiàn)提供的 —— 先是通過 Internet Explorer 5.0 然后通過 Mozilla 兼容的瀏覽器。請進(jìn)一步觀察這個對象。
XMLHttpRequest
使用 XMLHttpRequest
,可以用 javascript 發(fā)出到服務(wù)器的請求,并在不阻塞用戶的情況下處理響應(yīng)。在創(chuàng)建 Web 站點并用 XMLHttpRequest
在客戶機(jī)瀏覽器上無刷新地執(zhí)行屏幕更新的同時,它還提供了更多靈活性和豐富的用戶體驗。
XMLHttpRequest
應(yīng)用程序的示例包括 Google 的 Gmail 服務(wù)、Google 的 Suggest 動態(tài)查詢界面以及 MapQuest 的動態(tài)地圖界面。在下一節(jié),在演示圖書訂購應(yīng)用程序的設(shè)計和實現(xiàn)時,詳細(xì)介紹了如何使用 XMLHttpRequest
對象。
應(yīng)用程序設(shè)計
應(yīng)用程序的元素
基于 Web 的示例圖書訂購應(yīng)用程序包含以下用 AJAX 實現(xiàn)的客戶端函數(shù):
- 訂購 ID 驗證
- 一個 View Authors 列表
- 一個 View Publishers 列表
這里的目的是介紹 Web 頁面中的實時驗證 和頁面刷新 如何讓客戶交互更平滑、更有效。
應(yīng)用程序的結(jié)構(gòu)
圖 1 的圖表說明了示例圖書訂購應(yīng)用程序的設(shè)計架構(gòu):
圖 1. AJAX 架構(gòu) 應(yīng)用程序是一個使用 Java 服務(wù)器頁面(JSP)技術(shù)開發(fā)的單一 Web 頁面。用戶可以用 Web 瀏覽器(例如 Microsoft® Internet Explorer)調(diào)用 Web 頁面并輸入應(yīng)用程序要實時驗證的訂購 ID。在 ID 異步驗證的時候,用戶可以輸入更多信息。用戶可以根據(jù)作者 或出版者 查看圖書的書名。屏幕會根據(jù)用戶的選擇填充作者列表 或出版者列表。根據(jù)選擇,書名列表 會被填充。所有這些列表都是實時填充的 —— 換句話說,頁面沒有刷新,但是數(shù)據(jù)仍然來自后臺層。我們把這種現(xiàn)象叫做實時刷新。
從 圖 1 可以看出,XMLHttpRequest
javascript 對象幫助進(jìn)行實時異步處理。該對象采用 XML 格式通過 HTTP 對位于 Web 容器內(nèi)的 LibraryServlet
發(fā)出請求。然后 servlet 查詢數(shù)據(jù)庫、提取數(shù)據(jù)并發(fā)送回客戶機(jī),還是采用 XML 格式通過 HTTP 進(jìn)行傳送。請求和響應(yīng)都是在沒有刷新頁面的情況下實時發(fā)生的。
就是這一點使得 AJAX 如此強大。用戶不需要等候頁面重新載入才能完成,因為這里沒有頁面重載。
在 下一節(jié) 中,我將演示如何根據(jù)這個設(shè)計來實現(xiàn)圖書訂購應(yīng)用程序。我要介紹代碼并做一些分析。(要得到本教程的示例代碼,請 下載文件 x-ajax-library.war。)
實現(xiàn)應(yīng)用程序
用 AJAX 實現(xiàn)應(yīng)用程序
在這一節(jié),我們研究示例圖書訂購應(yīng)用程序的代碼,并進(jìn)一步查看每個基于 AJAX 的 javascript 組件:
查看代碼:驗證訂購 ID
先從驗證訂購 ID 的函數(shù) <input type="text" name="subscriptionID" onblur="validate(this.form)"/>
開始。這個代碼生成文本字段,用戶可以在里面輸入訂購 ID。用戶輸入 ID 并移到表單的下一個字段時,觸發(fā) onBlur
事件。這個事件調(diào)用 javascript 函數(shù) validate()
:
var req; function validate(formObj) { init(); req.onreadystatechange = subscriptionValidator; req.send("subscriptionID=" + formObj.subscriptionID.value); } |
validate()
函數(shù)接受 formObj
作為參數(shù)。它首先調(diào)用 init()
函數(shù):
function init() { if (window.XMLHttpRequest) { req = new XMLHttpRequest(); } else if (window.ActiveXObject) { req = new ActiveXObject("Microsoft.XMLHTTP"); } var url = "/Library/LibraryServlet"; req.open("POST", url, true); req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); } |
查看代碼: init()
現(xiàn)在看 init()
函數(shù)的工作(我們把代碼分成幾部分):
if (window.XMLHttpRequest) { req = new XMLHttpRequest(); } else if (window.ActiveXObject) { req = new ActiveXObject("Microsoft.XMLHTTP"); } |
init()
函數(shù)首先創(chuàng)建 XMLHttpRequest
對象。這個請求對象是 AJAX 的核心。它以 XML 格式發(fā)送和接收請求。這段代碼檢查瀏覽器對 XMLHttpRequest
對象的支持(多數(shù)瀏覽器都支持它)。如果使用 Microsoft Internet Explorer 5.0 以上版本,那么就執(zhí)行第二個條件。
req.open("POST", url, true); req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); |
代碼創(chuàng)建了 XMLHttpRequest
對象之后,需要設(shè)置某些請求屬性。在前面的代碼中,第一行設(shè)置請求方法、請求 URL 和請求的類型(是否異步)。它通過調(diào)用 XMLHttpRequest
對象上的 open()
方法做這件事。
這里我們要使用 POST
方法。理想情況下,當(dāng)需要在服務(wù)器上修改狀態(tài)時,請使用 POST
。我們的應(yīng)用程序并不修改狀態(tài),但我們?nèi)匀粌A向于使用 POST
。url
是要執(zhí)行的 servlet 的 URL。true
表明我們要異步地執(zhí)行請求。
對于 POST
方法,我們需要設(shè)置 Content-Type
這個請求頭。對于 GET
方法來說不需要這個設(shè)置。
function validate(formObj) { init(); req.onreadystatechange = subscriptionValidator; req.send("subscriptionID=" + formObj.subscriptionID.value); } |
查看代碼:回調(diào)句柄 1
繼續(xù)查看驗證方法,下面把 subscriptionValidator
回調(diào)句柄分配給 onreadystatechange
,請求狀態(tài)的每個變化都會觸發(fā)它。
這個回調(diào)句柄 都負(fù)責(zé)什么呢?因為正在異步地處理請求,所以需要一個回調(diào)句倆,從服務(wù)器返回完整響應(yīng)的時候調(diào)用它 —— 回調(diào)句柄是對訂購 ID 進(jìn)行驗證的地方(也就是編寫實際的驗證代碼的地方)。
句柄充當(dāng)偵聽器。它一直等待響應(yīng)完成。(稍后 將詳細(xì)介紹句柄代碼 。)為了發(fā)送請求,最后一行調(diào)用了 send()
方法。請求以名稱=值 對的形式發(fā)送。對于 GET
方法,請求作為 URL 的一部分發(fā)送,所以 send()
方法被傳遞了一個空參數(shù)。
請求被發(fā)送到 servlet。servlet 處理請求并實時地發(fā)回響應(yīng)。這就是 servlet 處理請求的方式。下一個代碼段表示了 LibraryServlet 的 doPost()
方法。
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String ID = null; ID = req.getParameter("subscriptionID"); if (ID != null) { String status = "<message>" + this.validID(ID) + "</message>"; this.writeResponse(resp, status); } } |
查看代碼:回調(diào)句柄 2
doPost()
方法從請求參數(shù)得到 subscriptionID
。為了驗證 ID,它調(diào)用 validID()
方法。這個方法驗證 ID,如果 ID 正確,則返回 true
,否則返回 false
。它用 XML 格式構(gòu)造返回狀態(tài),并調(diào)用 writeResponse()
方法來寫響應(yīng)?,F(xiàn)在來看 writeResponse()
方法。
public void writeResponse(HttpServletResponse resp, String output) throws IOException { resp.setContentType("text/xml"); resp.setHeader("Cache-Control", "no-cache"); resp.getWriter().write(output); } |
響應(yīng)用 XML 格式發(fā)送。第一行設(shè)置響應(yīng)的內(nèi)容類型為 text/xml
。第二行把頭 Cache-Control
的值設(shè)為 no-cache
。這個值是必需的。AJAX 要求響應(yīng)的輸出不能被瀏覽器緩存。最后一行調(diào)用 getWriter().write()
方法來寫響應(yīng)。
查看代碼:回調(diào)句柄 3
請求由 servlet 處理,響應(yīng)被發(fā)送回客戶機(jī)。請記住,所有這些都在后臺發(fā)生,沒有頁面刷新?,F(xiàn)在 前面 討論過的回調(diào)句柄方法會處理并解析響應(yīng):
function subscriptionValidator() { if (req.readystate == 4) { if (req.status == 200) { var messageObj = req.responseXML.getElementsByTagName("message")[0]; var message = messageObj.childNodes[0].nodeValue; if (message == "true") { msg.innerHTML = "Subscription is valid"; document.forms[0].order.disabled = false; } else { msg.innerHTML = "Subscription not valid"; document.forms[0].order.disabled = true; } } } } |
查看代碼:回到 XMLHttpRequest
如前所述,XMLHttpRequest
對象是構(gòu)造和發(fā)送請求的核心對象。它也負(fù)責(zé)讀取和解析從服務(wù)器返回的響應(yīng)。請看下面幾部分代碼。
if (req.readystate == 4) { if (req.status == 200) { |
前面的代碼檢查請求的狀態(tài)。如果請求處在就緒狀態(tài),就讀取和解析響應(yīng)。
就緒狀態(tài)是什么意思呢?當(dāng)請求對象的屬性 readystate
的值是 4
時,就意味著客戶機(jī)接收到了響應(yīng)而且接收完成。下面我們檢查請求的狀態(tài)(響應(yīng)是正常頁面還是錯誤頁面)。為了保證響應(yīng)正常,要檢查狀態(tài)的值是否為 200
。如果 status
的值是 200
,就會處理響應(yīng)。
var messageObj = req.responseXML.getElementsByTagName("message")[0]; var message = messageObj.childNodes[0].nodeValue; if (message == "true") { msg.innerHTML = "Subscription is valid"; document.forms[0].order.disabled = false; } else { msg.innerHTML = "Subscription not valid"; document.forms[0].order.disabled = true; } } |
接下來,請求對象通過調(diào)用 responseXML
屬性讀取響應(yīng)。請注意 servlet 用 XML 格式發(fā)送回響應(yīng),所以我們使用 responseXML
。如果響應(yīng)是以文本格式發(fā)送的,那么可以使用 responseText
屬性。
在這個示例中,我們處理 XML。servlet 把響應(yīng)構(gòu)建在一個 <message>
標(biāo)記中。要解析這個 XML 標(biāo)記,請在 XMLHttpRequest
對象的 responseXML
屬性上調(diào)用 getElementsByTagName()
方法。它得到標(biāo)記的名稱以及標(biāo)記的子值。根據(jù)解析到的值,格式化響應(yīng)并用 HTML 改寫。
現(xiàn)在就完成了對訂購 ID 的驗證,沒有頁面刷新。
查看代碼:查看作者、出版者和書名
其他的功能 —— 查看作者、查看出版者 和查看書名 —— 工作的方式類似。只是需要為每個功能定義獨立的句柄:
function displayList(field) { init(); titles.innerHTML = " "; req.onreadystatechange = listHandler; req.send("select=" + escape(field)); } function displayTitles(formObj) { init(); var index = formObj.list.selectedIndex; var val = formObj.list.options[index].value; req.onreadystatechange = titlesHandler; req.send("list=" + val); } |
請記住,示例應(yīng)用程序允許用戶根據(jù)作者和出版者查看書名。所以顯示的或者是作者列表 或者是出版者列表。在這類場景中,應(yīng)用程序只能根據(jù)用戶的選擇調(diào)用一個回調(diào)句柄 —— 換句話說,對于作者和出版者列表,只有一個 listHandler
回調(diào)句柄。
顯示書名列表需要使用 titlesHandler
。其余的功能仍然一樣:servlet 處理請求,用 XML 格式寫回響應(yīng)。然后讀取、解析、格式化響應(yīng),用 HTML 改寫??梢杂?HTML 把列表呈現(xiàn)為 select......options
標(biāo)記。這個示例代碼段顯示了 titlesHandler
方法。
var temp = "<select name=\"titles\" multiple\>"; for (var i=0; i<index; i++) { var listObj = req.responseXML.getElementsByTagName("list")[i]; temp = temp + "<option value=" + i +">" + listObj.childNodes[0].nodeValue + "</option>"; } temp = temp + "</select>"; titles.innerHTML = temp; |
迄今為止,我們演示了如何實現(xiàn)實時驗證和刷新。使用 AJAX,可以選擇多種方式給 Web 站點的用戶交互添加特色。下面運行應(yīng)用程序。
運行和測試應(yīng)用程序
運行應(yīng)用程序
請下載示例代碼 wa-ajax-Library.war 并把它拷貝到 Tomcat 的 Webapp 目錄(例如,c:\Tomcat 5.0\Webapps)。要啟動 Tomcat 服務(wù)器,請輸入以下命令:
cd bin C:\Tomcat 5.0\bin> catalina.bat start |
Tomcat 現(xiàn)在啟動了,AJAX Web 應(yīng)用程序也部署在其中。
測試應(yīng)用程序
要測試應(yīng)用程序:
- 請打開 Web 瀏覽器,指向
http://localhost:tomcatport/Library/order.jsp
,其中變量 tompcatport 是 Tomcat 服務(wù)器運行所在的端口。 將看到訂購屏幕。
- 在 Enter Subscription ID 字段中,輸入用戶 ID(“John” 除外)并離開字段。
向服務(wù)器異步發(fā)出的訂購 ID 請求會被驗證。可以看到 “Subscription not valid” 消息,如 圖 2 所示:
圖 2. “Subcription not valid” 屏幕
應(yīng)用程序在不刷新瀏覽器的情況下,異步地驗證用戶并提供運行時驗證。
- 輸入用戶 ID 值 John。
將看到消息 “Subscription is valid”。訂購有效之后,應(yīng)用程序就激活 Order 按鈕。
- 請選擇 By Author 或 By Publisher 按鈕分別填充作者或出版者下拉列表。
- 從下拉列表選擇一個作者或出版者。
書名區(qū)域被動態(tài)填充(如 圖 3 所示)。
圖 3. “Subcription is valid”屏幕
在選擇作者或出版者時,應(yīng)用程序請求服務(wù)器在運行時提供來自服務(wù)器的與選中作者或出版者對應(yīng)的書名信息。在不刷新瀏覽器的情況下顯示書名信息。
現(xiàn)在成功安裝和測試了示例 AJAX 應(yīng)用程序。
結(jié)束語
結(jié)束語
AJAX 從開始至今已經(jīng)走了很長的路。我們相信 AJAX 不僅僅可以用作設(shè)計模式,不過 AJAX 仍然有一些問題:
- 瀏覽器對
XMLHttpRequest
對象的支持可能是個限制。大多數(shù)瀏覽器都支持 XMLHttpRequest
對象,但是有少數(shù)不支持(通常是較老版本的瀏覽器)。 - AJAX 最適合顯示少量數(shù)據(jù)。如果要處理大量數(shù)據(jù)并在列表中實時顯示,那么 AJAX 可能不是合適的解決方案。
- AJAX 非常依賴于 javascript。如果瀏覽器不支持 javascript 或者用戶禁止了腳本選項,那么根本就不能利用 AJAX。
- AJAX 異步的性質(zhì)不會保證多個請求的同步請求處理。如果需要對驗證或刷新進(jìn)行優(yōu)先處理,那么要相應(yīng)地設(shè)計應(yīng)用程序。
即使有這些可能的問題,AJAX 仍然是提高 Web 頁面和解決頁面重載問題的最佳解決方案。
下載
描述 | 名字 | 大小 | 下載方法 |
Web application Web archives | wa-ajax-Library.war | 8KB | FTP |
參考資料
學(xué)習(xí) 討論 作者簡介
|
| | Naveen Balani 把他的大多數(shù)時間花在設(shè)計和開發(fā)基于 J2EE/SOA 的框架和產(chǎn)品上。他為 IBM developerWorks 編寫了各種不同的文章,涉及的主題包括:ESB、SOA、JMS、WebServices Architectures、CICS、AXIS、DB2 XML Extender、WebSphere Studio、MQSeries、Java Wireless Devices 和 DB2 Everyplace for Palm、J2ME、MIDP、Java-Nokia、Visual Studio .Net 以及無線數(shù)據(jù)同步。可以通過他的電子郵件 naveenbalani@rediffmail.com 與他聯(lián)系。 |
|
| | Rajeev Hathi 目前在 Satyam 計算機(jī)有限公司擔(dān)任高級系統(tǒng)分析員。他把時間花在設(shè)計和開發(fā)基于 J2EE 的框架上。他喜歡研究新技術(shù)和新領(lǐng)域。他的業(yè)余愛好是運動和音樂??梢酝ㄟ^他的電子郵件 rajeev_hathi@hotmail.com 與他聯(lián)系。 |