2005 年 10 月 20 日
在 Web 應用程序開(kāi)發(fā)中,頁(yè)面重載循環(huán)是最大的一個(gè)使用障礙,對于 Java™ 開(kāi)發(fā)人員來(lái)說(shuō)也是一個(gè)嚴峻的挑戰。在這個(gè)系列中,作者 Philip McCarthy 介紹了一種創(chuàng )建動(dòng)態(tài)應用程序體驗的開(kāi)創(chuàng )性方式。Ajax(異步 JavaScript 和 XML)是一種編程技術(shù),它允許為基于 Java 的 Web 應用程序把 Java 技術(shù)、XML 和 JavaScript 組合起來(lái),從而打破頁(yè)面重載的范式。
Ajax(即異步 JavaScript 和 XML)是一種 Web 應用程序開(kāi)發(fā)的手段,它采用客戶(hù)端腳本與 Web 服務(wù)器交換數據。所以,不必采用會(huì )中斷交互的完整頁(yè)面刷新,就可以動(dòng)態(tài)地更新 Web 頁(yè)面。使用 Ajax,可以創(chuàng )建更加豐富、更加動(dòng)態(tài)的 Web 應用程序用戶(hù)界面,其即時(shí)性與可用性甚至能夠接近本機桌面應用程序。
Ajax 不是一項技術(shù),而更像是一個(gè) 模式 —— 一種識別和描述有用的設計技術(shù)的方式。Ajax 是新穎的,因為許多開(kāi)發(fā)人員才剛剛開(kāi)始知道它,但是所有實(shí)現 Ajax 應用程序的組件都已經(jīng)存在若干年了。它目前受到重視是因為在 2004 和 2005 年出現了一些基于 Ajax 技術(shù)的非常棒的動(dòng)態(tài) Web UI,最著(zhù)名的就是 Google 的 GMail 和 Maps 應用程序,以及照片共享站點(diǎn) Flickr。這些用戶(hù)界面具有足夠的開(kāi)創(chuàng )性,有些開(kāi)發(fā)人員稱(chēng)之為“Web 2.0”,因此對 Ajax 應用程序的興趣飛速上升。
在這個(gè)系列中,我將提供使用 Ajax 開(kāi)發(fā)應用程序需要的全部工具 。在第一篇文章中,我將解釋 Ajax 背后的概念,演示為基于 Java 的 Web 應用程序創(chuàng )建 Ajax 界面的基本步驟。我將使用代碼示例演示讓 Ajax 應用程序如此動(dòng)態(tài)的服務(wù)器端 Java 代碼和客戶(hù)端 JavaScript。最后,我將指出 Ajax 方式的一些不足,以及在創(chuàng )建 Ajax 應用程序時(shí)應當考慮的一些更廣的可用性和訪(fǎng)問(wèn)性問(wèn)題。
可以用 Ajax 增強傳統的 Web 應用程序,通過(guò)消除頁(yè)面裝入從而簡(jiǎn)化交互。為了演示這一點(diǎn),我采用一個(gè)簡(jiǎn)單的購物車(chē)示例,在向里面添加項目時(shí),它會(huì )動(dòng)態(tài)更新。這項技術(shù)如果整合到在線(xiàn)商店,那么用戶(hù)可以持續地瀏覽和向購物車(chē)中添加項目,而不必在每次點(diǎn)擊之后都等候完整的頁(yè)面更新。雖然這篇文章中的有些代碼特定于購物車(chē)示例,但是演示的技術(shù)可以應用于任何 Ajax 應用程序。清單 1 顯示了購物車(chē)示例使用的有關(guān) HTML 代碼,整篇文章中都會(huì )使用這個(gè) HTML。
|
![]() |
|
Ajax 交互開(kāi)始于叫作 XMLHttpRequest 的 JavaScript 對象。顧名思義,它允許客戶(hù)端腳本執行 HTTP 請求,并解析 XML 服務(wù)器響應。Ajax 往返過(guò)程的第一步是創(chuàng )建 XMLHttpRequest 的實(shí)例。在 XMLHttpRequest 對象上設置請求使用的 HTTP 方法(GET 或 POST)以及目標 URL。
現在,您還記得 Ajax 的第一個(gè) a 是代表 異步(asynchronous) 嗎?在發(fā)送 HTTP 請求時(shí),不想讓瀏覽器掛著(zhù)等候服務(wù)器響應。相反,您想讓瀏覽器繼續對用戶(hù)與頁(yè)面的交互進(jìn)行響應,并在服務(wù)器響應到達時(shí)再進(jìn)行處理。為了實(shí)現這個(gè)要求,可以在 XMLHttpRequest 上注冊一個(gè)回調函數,然后異步地分派 XMLHttpRequest。然后控制就會(huì )返回瀏覽器,當服務(wù)器響應到達時(shí),會(huì )調用回調函數。
在 Java Web 服務(wù)器上,請求同其他 HttpServletRequest 一樣到達。在解析了請求參數之后,servlet 調用必要的應用程序邏輯,把響應序列化成 XML,并把 XML 寫(xiě)入 HttpServletResponse。
回到客戶(hù)端時(shí),現在調用注冊在 XMLHttpRequest 上的回調函數,處理服務(wù)器返回的 XML 文檔。最后,根據服務(wù)器返回的數據,用 JavaScript 操縱頁(yè)面的 HTML DOM,把用戶(hù)界面更新。圖 1 是 Ajax 往返過(guò)程的順序圖。

現在您對 Ajax 往返過(guò)程有了一個(gè)高層面的認識。下面我將放大其中的每一步驟,進(jìn)行更詳細的觀(guān)察。如果過(guò)程中迷了路,請回頭看圖 1 —— 由于 Ajax 方式的異步性質(zhì),所以順序并非十分簡(jiǎn)單。
![]() |
|
我將從 Ajax 序列的起點(diǎn)開(kāi)始:創(chuàng )建和分派來(lái)自瀏覽器的 XMLHttpRequest。不幸的是,不同的瀏覽器創(chuàng )建 XMLHttpRequest 的方法各不相同。清單 2 的 JavaScript 函數消除了這些依賴(lài)于瀏覽器的技巧,它可以檢測當前瀏覽器要使用的正確方式,并返回一個(gè)可以使用的 XMLHttpRequest。最好是把它當作輔助代碼:只要把它拷貝到 JavaScript 庫,并在需要 XMLHttpRequest 的時(shí)候使用它就可以了。
|
稍后我將討論處理那些不支持 XMLHttpRequest 的瀏覽器的技術(shù)。目前,示例假設清單 2 的 newXMLHttpRequest 函數總能返回 XMLHttpRequest 實(shí)例。
返回示例的購物車(chē)場(chǎng)景,我想要當用戶(hù)在目錄項目上點(diǎn)擊 Add to Cart 時(shí)啟動(dòng) Ajax 交互。名為 addToCart() 的 onclick 處理函數負責通過(guò) Ajax 調用來(lái)更新購物車(chē)的狀態(tài)(請參閱 清單 1)。正如清單 3 所示,addToCart() 需要做的第一件事是通過(guò)調用清單 2 的 newXMLHttpRequest() 函數得到 XMLHttpRequest 對象。接下來(lái),它注冊一個(gè)回調函數,用來(lái)接收服務(wù)器響應(我稍后再詳細解釋這一步;請參閱 清單 6)。
因為請求會(huì )修改服務(wù)器上的狀態(tài),所以我將用 HTTP POST 做這個(gè)工作。通過(guò) POST 發(fā)送數據要求三個(gè)步驟。第一,需要打開(kāi)與要通信的服務(wù)器資源的 POST 連接 —— 在這個(gè)示例中,服務(wù)器資源是一個(gè)映射到 URL cart.do 的 servlet。然后,我在 XMLHttpRequest 上設置一個(gè)頭,指明請求的內容是表單 編碼的數據。最后,我用表單編碼的數據作為請求體發(fā)送請求。
清單 3 把這些步驟放在了一起。
|
這就是建立 Ajax 往返過(guò)程的第一部分,即創(chuàng )建和分派來(lái)自客戶(hù)機的 HTTP 請求。接下來(lái)是用來(lái)處理請求的 Java servlet 代碼。
![]() |
|
用 servlet 處理 XMLHttpRequest,與處理普通的瀏覽器 HTTP 請求一樣??梢杂?HttpServletRequest.getParameter() 得到在 POST 請求體中發(fā)送的表單編碼數據。Ajax 請求被放進(jìn)與來(lái)自應用程序的常規 Web 請求一樣的 HttpSession 中。對于示例購物車(chē)場(chǎng)景來(lái)說(shuō),這很有用,因為這讓我可以把購物車(chē)狀態(tài)封裝在 JavaBean 中,并在請求之間在會(huì )話(huà)中維持這個(gè)狀態(tài)。
清單 4 是處理 Ajax 請求、更新購物車(chē)的簡(jiǎn)單 servlet 的一部分。Cart bean 是從用戶(hù)會(huì )話(huà)中獲得的,并根據請求參數更新它的狀態(tài)。然后 Cart 被序列化成 XML,XML 又被寫(xiě)入 ServletResponse。重要的是把響應的內容類(lèi)型設置為 application/xml,否則 XMLHttpRequest 不會(huì )把響應內容解析成 XML DOM。
|
清單 5 顯示了 Cart.toXml() 方法生成的示例 XML。它很簡(jiǎn)單。請注意 cart 元素的 generated 屬性,它是 System.currentTimeMillis() 生成的一個(gè)時(shí)間戳。
|
如果查看應用程序源代碼(可以從 下載 一節得到)中的 Cart.java,可以看到生成 XML 的方式只是把字符串添加在一起。雖然對這個(gè)示例來(lái)說(shuō)足夠了,但是對于從 Java 代碼生成 XML 來(lái)說(shuō)則是最差的方式。我將在這個(gè)系列的下一期中介紹一些更好的方式。
現在您已經(jīng)知道了 CartServlet 響應 XMLHttpRequest 的方式。下一件事就是返回客戶(hù)端,查看如何用 XML 響應更新頁(yè)面狀態(tài)。
![]() |
|
XMLHttpRequest 的 readyState 屬性是一個(gè)數值,它指出請求生命周期的狀態(tài)。它從 0(代表“未初始化”)變化到 4(代表“完成”)。每次 readyState 變化時(shí),readystatechange 事件就觸發(fā),由 onreadystatechange 屬性指定的事件處理函數就被調用。
在 清單 3 中已經(jīng)看到了如何調用 getReadyStateHandler() 函數創(chuàng )建事件處理函數。然后把這個(gè)事件處理函數分配給 onreadystatechange 屬性。getReadyStateHandler() 利用了這樣一個(gè)事實(shí):函數是 JavaScript 中的一級對象。這意味著(zhù)函數可以是其他函數的參數,也可以創(chuàng )建和返回其他函數。getReadyStateHandler() 的工作是返回一個(gè)函數,檢查 XMLHttpRequest 是否已經(jīng)完成,并把 XML 響應傳遞給調用者指定的事件處理函數。清單 6 是 getReadyStateHandler() 的代碼。
|
![]() |
|
關(guān)于 getReadyStateHandler()
getReadyStateHandler() 是段相對復雜的代碼,特別是如果您不習慣閱讀 JavaScript 的話(huà)。但是通過(guò)把這個(gè)函數放在 JavaScript 庫中,就可以處理 Ajax 服務(wù)器響應,而不必處理 XMLHttpRequest 的內部細節。重要的是要理解如何在自己的代碼中使用 getReadyStateHandler()。
在 清單 3 中看到了 getReadyStateHandler() 像這樣被調用:handlerFunction = getReadyStateHandler(req, updateCart)。在這個(gè)示例中,getReadyStateHandler() 返回的函數將檢查在 req 變量中的 XMLHttpRequest 是否已經(jīng)完成,然后用響應的 XML 調用名為 updateCart 的函數。
清單 7 是 updateCart() 本身的代碼。函數用 DOM 調用檢查購物車(chē)的 XML 文檔,然后更新 Web 頁(yè)面(請參閱 清單 1),反映新的購物車(chē)內容。這里的重點(diǎn)是用來(lái)從 XML DOM 提取數據的調用。cart 元素的 generated 屬性是在 Cart 序列化為 XML 時(shí)生成的一個(gè)時(shí)間戳,檢查它可以保證新的購物車(chē)數據不會(huì )被舊的數據覆蓋。Ajax 請求天生是異步的,所以這個(gè)檢查可以處理服務(wù)器響應未按次序到達的情況。
|
到此,整個(gè) Ajax 往返過(guò)程完成了,但是您可能想讓 Web 應用程序運行一下查看實(shí)際效果(請參閱 下載 一節)。這個(gè)示例非常簡(jiǎn)單,有很多需要改進(jìn)之處。例如,我包含了從購物車(chē)中清除項目的服務(wù)器端代碼,但是無(wú)法從 UI 訪(fǎng)問(wèn)它。作為一個(gè)好的練習,請試著(zhù)在應用程序現有的 JavaScript 代碼之上構建出能夠實(shí)現這個(gè)功能的代碼。
![]() |
|
就像任何技術(shù)一樣,使用 Ajax 也有許多出錯的可能性。我目前在這里討論的問(wèn)題還缺乏容易的解決方案,但是會(huì )隨著(zhù) Ajax 的成熟而改進(jìn)。隨著(zhù)開(kāi)發(fā)人員社區增加開(kāi)發(fā) Ajax 應用程序的經(jīng)驗,將會(huì )記錄下最佳實(shí)踐和指南。
Ajax 開(kāi)發(fā)人員面臨的一個(gè)最大問(wèn)題是:在沒(méi)有 XMLHttpRequest 可用時(shí)該如何響應?雖然主要的現代瀏覽器都支持 XMLHttpRequest,但仍然有少數用戶(hù)的瀏覽器不支持,或者瀏覽器的安全設置阻止使用 XMLHttpRequest。如果開(kāi)發(fā)的 Web 應用程序要部署在企業(yè)內部網(wǎng),那么可能擁有指定支持哪種瀏覽器的權力,從而可以認為 XMLHttpRequest 總能使用。但是,如果要部署在公共 Web 上,那么就必須當心,如果假設 XMLHttpRequest 可用,那么就可能會(huì )阻止那些使用舊的瀏覽器、殘疾人專(zhuān)用瀏覽器和手持設備上的輕量級瀏覽器的用戶(hù)使用您的應用程序。
所以,您應當努力讓?xiě)贸绦?#8220;平穩降級”,在沒(méi)有 XMLHttpRequest 支持的瀏覽器中也能夠工作。在購物車(chē)的示例中,把應用程序降級的最好方式可能是讓 Add to Cart 按鈕執行一個(gè)常規的表單提交,刷新頁(yè)面來(lái)反映購物車(chē)更新后的狀態(tài)。Ajax 的行為應當在頁(yè)面裝入的時(shí)候就通過(guò) JavaScript 添加到頁(yè)面,只有在 XMLHttpRequest 可用時(shí)才把 JavaScript 事件處理函數附加到每個(gè) Add to Cart 按鈕。另一種方式是在用戶(hù)登錄時(shí)檢測 XMLHttpRequest 是否可用,然后相應地提供應用程序的 Ajax 版本或基于表單的普通版本。
關(guān)于 Ajax 應用程序的某些可用性問(wèn)題比較普遍。例如,讓用戶(hù)知道他們的輸入已經(jīng)注冊了可能是重要的,因為沙漏光標和 spinning 瀏覽器的常用反饋機制“throbber”對 XMLHttpRequest 不適用。一種技術(shù)是用“Now updating...”類(lèi)型的信息替換 Submit 按鈕,這樣用戶(hù)在等候響應期間就不會(huì )反復單擊按鈕了。
另一個(gè)問(wèn)題是,用戶(hù)可能沒(méi)有注意到他們正在查看的頁(yè)面的某一部分已經(jīng)更新了??梢允褂貌煌目梢暭夹g(shù),把用戶(hù)的眼球帶到頁(yè)面的更新區域,從而緩解這個(gè)問(wèn)題。由 Ajax 更新頁(yè)面造成的其他問(wèn)題還包括:“破壞了”瀏覽器的后退按鈕,地址欄中的 URL 也無(wú)法反映頁(yè)面的整個(gè)狀態(tài),妨礙了設置書(shū)簽。請參閱 參考資料 一節,獲得專(zhuān)門(mén)解決 Ajax 應用程序可用性問(wèn)題的文章。
用 Ajax 實(shí)現代替普通的基于表單的 UI,會(huì )大大提高對服務(wù)器發(fā)出的請求數量。例如,一個(gè)普通的 Google Web 搜索對服務(wù)器只有一個(gè)請求,是在用戶(hù)提交搜索表單時(shí)出現的。而 Google Suggest 試圖自動(dòng)完成搜索術(shù)語(yǔ),它要在用戶(hù)輸入時(shí)向服務(wù)器發(fā)送多個(gè)請求。在開(kāi)發(fā) Ajax 應用程序時(shí),要注意將要發(fā)送給服務(wù)器的請求數量以及由此造成的服務(wù)器負荷。降低服務(wù)器負載的辦法是,在客戶(hù)機上對請求進(jìn)行緩沖并且緩存服務(wù)器響應(如果可能的話(huà))。還應該嘗試將 Ajax Web 應用程序設計為在客戶(hù)機上執行盡可能多的邏輯,而不必聯(lián)絡(luò )服務(wù)器。
非常重要的是,要理解無(wú)法保證 XMLHttpRequest 會(huì )按照分派它們的順序完成。實(shí)際上,應當假設它們不會(huì )按順序完成,并且在設計應用程序時(shí)把這一點(diǎn)記在心上。在購物車(chē)的示例中,使用最后更新的時(shí)間戳來(lái)確保新的購物車(chē)數據不會(huì )被舊的數據覆蓋(請參閱 清單 7)。這個(gè)非?;镜姆绞娇梢杂糜谫徫镘?chē)場(chǎng)景,但是可能不適合其他場(chǎng)景。所以在設計時(shí)請考慮如何處理異步的服務(wù)器響應。
![]() |
|
現在您對 Ajax 的基本原則應當有了很好的理解,對參與 Ajax 交互的客戶(hù)端和服務(wù)器端組件也應當有了初步的知識。這些是基于 Java 的 Ajax Web 應用程序的構造塊。另外,您應當理解了伴隨 Ajax 方式的一些高級設計問(wèn)題。創(chuàng )建成功的 Ajax 應用程序要求整體考慮,從 UI 設計到 JavaScript 設計,再到服務(wù)器端架構;但是您現在應當已經(jīng)武裝了考慮其他這些方面所需要的核心 Ajax 知識。
如果使用這里演示的技術(shù)編寫(xiě)大型 Ajax 應用程序的復雜性讓您覺(jué)得恐慌,那么有好消息給您。由于 Struts、Spring 和 Hibernate 這類(lèi)框架的發(fā)展把 Web 應用程序開(kāi)發(fā)從底層 Servlet API 和 JDBC 的細節中抽象出來(lái),所以正在出現簡(jiǎn)化 Ajax 開(kāi)發(fā)的工具包。其中有些只側重于客戶(hù)端,提供了向頁(yè)面添加可視效果的簡(jiǎn)便方式,或者簡(jiǎn)化了對 XMLHttpRequest 的使用。有些則走得更遠,提供了從服務(wù)器端代碼自動(dòng)生成 Ajax 接口的方式。這些框架替您完成了繁重的任務(wù),所以您可以采用更高級的方式進(jìn)行 Ajax 開(kāi)發(fā)。我在這個(gè)系列中將研究其中的一些。
Ajax 社區正在快速前進(jìn),所以會(huì )有大量有價(jià)值的信息涌現。在閱讀這個(gè)系列的下一期之前,我建議您參考 參考資料 一節中列出的文章,特別是如果您是剛接觸 Ajax 或客戶(hù)端開(kāi)發(fā)的話(huà)。您還應當花些時(shí)間研究示例源代碼并考慮一些增強它的方式。
在這個(gè)系列的下一篇文章中,我將深入討論 XMLHttpRequest API,并推薦一些從 JavaBean 方便地創(chuàng )建 XML 的方式。我還將介紹替代 XML 進(jìn)行 Ajax 數據傳遞的方式,例如 JSON(JavaScript Object Notation)輕量級數據交換格式。
聯(lián)系客服