作為J2EE開發(fā)人員,我們似乎經(jīng)常關(guān)注“后端機制(backend mechanics)”。我們通常會忘記,J2EE的主要成功之處在Web應(yīng)用程序方面;許多原因使得人們喜歡利用Web開發(fā)應(yīng)用程序,但主要還是因為其易于部署的特點允許站點以盡可能低的成本擁有上百萬的用戶。遺憾的是,在過去幾年中,我們在后端投入了太多的時間,而在使我們的Web用戶界面對用戶自然和響應(yīng)靈敏方面卻投入不足。
本文介紹一種方法,Ajax,使用它可以構(gòu)建更為動態(tài)和響應(yīng)更靈敏的Web應(yīng)用程序。該方法的關(guān)鍵在于對瀏覽器端的JavaScript、DHTML和與服務(wù)器異步通信的組合。本文也演示了啟用這種方法是多么簡單:利用一個Ajax框架(指DWR)構(gòu)造一個應(yīng)用程序,它直接從瀏覽器與后端服務(wù)進行通信。如果使用得當,這種強大的力量可以使應(yīng)用程序更加自然和響應(yīng)靈敏,從而提升用戶的瀏覽體驗。 該應(yīng)用程序中所使用的示例代碼已打包為單獨的WAR文件,可供下載。 簡介 術(shù)語Ajax用來描述一組技術(shù),它使瀏覽器可以為用戶提供更為自然的瀏覽體驗。在Ajax之前,Web站點強制用戶進入提交/等待/重新顯示范例,用戶的動作總是與服務(wù)器的“思考時間”同步。Ajax提供與服務(wù)器異步通信的能力,從而使用戶從請求/響應(yīng)的循環(huán)中解脫出來。借助于Ajax,可以在用戶單擊按鈕時,使用JavaScript和DHTML立即更新UI,并向服務(wù)器發(fā)出異步請求,以執(zhí)行更新或查詢數(shù)據(jù)庫。當請求返回時,就可以使用JavaScript和CSS來相應(yīng)地更新UI,而不是刷新整個頁面。最重要的是,用戶甚至不知道瀏覽器正在與服務(wù)器通信:Web站點看起來是即時響應(yīng)的。 雖然Ajax所需的基礎(chǔ)架構(gòu)已經(jīng)出現(xiàn)了一段時間,但直到最近異步請求的真正威力才得到利用。能夠擁有一個響應(yīng)極其靈敏的Web站點確實激動人心,因為它最終允許開發(fā)人員和設(shè)計人員使用標準的HTML/CSS/JavaScript堆棧創(chuàng)建“桌面風(fēng)格的(desktop-like)”可用性。 通常,在J2EE中,開發(fā)人員過于關(guān)注服務(wù)和持久性層的開發(fā),以至于用戶界面的可用性已經(jīng)落后。在一個典型的J2EE開發(fā)周期中,常常會聽到這樣的話,“我們沒有可投入UI的時間”或“不能用HTML實現(xiàn)”。但是,以下Web站點證明,這些理由再也站不住腳了: 所有這些Web站點都告訴我們,Web應(yīng)用程序不必完全依賴于從服務(wù)器重新載入頁面來向用戶呈現(xiàn)更改。一切似乎就在瞬間發(fā)生。簡而言之,在涉及到用戶界面的響應(yīng)靈敏度時,基準設(shè)得更高了。 定義Ajax Adaptive Path公司的Jesse James Garrett這樣定義Ajax: Ajax不是一種技術(shù)。實際上,它由幾種蓬勃發(fā)展的技術(shù)以新的強大方式組合而成。Ajax包含:
這非常好,但為什么要以Ajax命名呢?其實術(shù)語Ajax是由Jesse James Garrett創(chuàng)造的,他說它是“Asynchronous JavaScript + XML的簡寫”。 Ajax的工作原理 Ajax的核心是JavaScript對象XmlHttpRequest。該對象在Internet Explorer 5中首次引入,它是一種支持異步請求的技術(shù)。簡而言之,XmlHttpRequest使您可以使用JavaScript向服務(wù)器提出請求并處理響應(yīng),而不阻塞用戶。 在創(chuàng)建Web站點時,在客戶端執(zhí)行屏幕更新為用戶提供了很大的靈活性。下面是使用Ajax可以完成的功能:
一切皆有可能!但愿它能夠激發(fā)您開始開發(fā)自己的基于Ajax的站點。然而,在開始之前,讓我們介紹一個現(xiàn)有的Web站點,它遵循傳統(tǒng)的提交/等待/重新顯示的范例,我們還將討論Ajax如何提升用戶體驗。 Ajax可用于那些場景?——一個例子:MSN Money頁面 前幾天,在瀏覽MSN Money頁面的時候,有一篇關(guān)于房地產(chǎn)投資的文章引起了我的好奇心。我決定使用站點的“Rate this article”(評價本文)功能,鼓勵其他的用戶花一點時間來閱讀這篇文章。在我單擊vote按鈕并等待了一會兒之后,整個頁面被刷新,在原來投票問題所在的地方出現(xiàn)了一個漂亮的感謝畫面。
而Ajax能夠使用戶的體驗更加愉快,它可以提供響應(yīng)更加靈敏的UI,并消除頁面刷新所帶來的閃爍。目前,由于要刷新整個頁面,需要傳送大量的數(shù)據(jù),因為必須重新發(fā)送整個頁面。如果使用Ajax,服務(wù)器可以返回一個包含了感謝信息的500字節(jié)的消息,而不是發(fā)送26,813字節(jié)的消息來刷新整個頁面。即使使用的是高速Internet,傳送26K和1/2K的差別也非常大。同樣重要的是,只需要刷新與投票相關(guān)的一小節(jié),而不是刷新整個屏幕。 讓我們利用Ajax實現(xiàn)自己的基本投票系統(tǒng)。 原始的Ajax:直接使用XmlHttpRequest 如上所述,Ajax的核心是JavaScript對象XmlHttpRequest。下面的示例文章評價系統(tǒng)將帶您熟悉Ajax的底層基本知識:http:///ajax-demo/raw-ajax.html。注:如果您已經(jīng)在本地WebLogic容器中安裝了ajax-demo.war,可以導(dǎo)航到http://localhost:7001/ajax-demo/raw-ajax.html, 瀏覽應(yīng)用程序,參與投票,并親眼看它如何運轉(zhuǎn)。熟悉了該應(yīng)用程序之后,繼續(xù)閱讀,進一步了解其工作原理細節(jié)。 首先,您擁有一些簡單的定位點標記,它連接到一個JavaScriptcastVote(rank)函數(shù)。function castVote(rank) { var url = "/ajax-demo/static-article-ranking.html"; var callback = processAjaxResponse; executeXhr(callback, url); } 該函數(shù)為您想要與之通信的服務(wù)器資源創(chuàng)建一個URL并調(diào)用內(nèi)部函數(shù)executeXhr,提供一個回調(diào)JavaScript函數(shù),一旦服務(wù)器響應(yīng)可用,該函數(shù)就被執(zhí)行。由于我希望它運行在一個簡單的Apache環(huán)境中,“cast vote URL”只是一個簡單的HTML頁面。在實際情況中,被調(diào)用的URL將記錄票數(shù)并動態(tài)地呈現(xiàn)包含投票總數(shù)的響應(yīng)。 下一步是發(fā)出一個XmlHttpRequest請求:function executeXhr(callback, url) { // branch for native XMLHttpRequest object if (window.XMLHttpRequest) { req = new XMLHttpRequest(); req.onreadystatechange = callback; req.open("GET", url, true); req.send(null); } // branch for IE/Windows ActiveX version else if (window.ActiveXObject) { req = new ActiveXObject("Microsoft.XMLHTTP"); if (req) { req.onreadystatechange = callback; req.open("GET", url, true); req.send(); } } } 如您所見,執(zhí)行一個XmlHttpRequest并不簡單,但非常直觀。和平常一樣,在JavaScript領(lǐng)域,大部分的工作量都花在確保瀏覽器兼容方面。在這種情況下,首先要確定XmlHttpRequest是否可用。如果不能用,很可能要使用Internet Explorer,這樣就要使用所提供的ActiveX實現(xiàn)。 executeXhr()方法中最關(guān)鍵的部分是這兩行: req.onreadystatechange = callback; req.open("GET", url, true); 第一行定義了JavaScript回調(diào)函數(shù),您希望一旦響應(yīng)就緒它就自動執(zhí)行,而req.open()方法中所指定的“true”標志說明您想要異步執(zhí)行該請求。 一旦服務(wù)器處理完XmlHttpRequest并返回給瀏覽器,使用req.onreadystatechange指派所設(shè)置的回調(diào)方法將被自動調(diào)用。function processAjaxResponse() { // only if req shows "loaded" if (req.readyState == 4) { // only if "OK" if (req.status == 200) { 502 502‘votes‘).innerHTML = req.responseText; } else { alert("There was a problem retrieving the XML data: " + req.statusText); } } } 該代碼相當簡潔,并且使用了幾個幻數(shù),這使得難以一下子看出發(fā)生了什么。為了弄清楚這一點,下面的表格(引用自http://developer.apple.com/internet/webcontent/xmlhttpreq.html)列舉了常用的XmlHttpRequest對象屬性。
現(xiàn)在processVoteResponse()函數(shù)開始顯示出其意義了。它首先檢查XmlHttpRequest的整體狀態(tài)以保證它已經(jīng)完成(readyStatus == 4),然后根據(jù)服務(wù)器的設(shè)定詢問請求狀態(tài)。如果一切正常(status == 200),就使用innerHTML屬性重寫DOM的“votes”節(jié)點的內(nèi)容。 既然您親眼看到了XmlHttpRequest對象是如何工作的,就讓我們利用一個旨在簡化JavaScript與Java應(yīng)用程序之間的異步通信的框架來對具體的細節(jié)進行抽象。 Ajax: DWR方式 按照與文章評價系統(tǒng)相同的流程,我們將使用Direct Web Remoting(DWR)框架實現(xiàn)同樣的功能。 假定文章和投票結(jié)果存儲在一個數(shù)據(jù)庫中,使用某種對象/關(guān)系映射技術(shù)來完成抽取工作。為了部署起來盡可能地簡單,我們不會使用數(shù)據(jù)庫進行持久性存儲。此外,為使應(yīng)用程序盡可能通用,也不使用Web框架。相反,應(yīng)用程序?qū)囊粋€靜態(tài)HTML文件開始,可以認為它由服務(wù)器動態(tài)地呈現(xiàn)。除了這些簡化措施,應(yīng)用程序還應(yīng)該使用Spring Framework關(guān)聯(lián)一切,以便輕松看出如何在一個“真實的”應(yīng)用程序中使用DWR。 現(xiàn)在應(yīng)該下載示例應(yīng)用程序并熟悉它。該應(yīng)用程序被壓縮為標準的WAR文件,因此您可以把它放置到任何一個Web容器中——無需進行配置。部署完畢之后,就可以導(dǎo)航到http://localhost:7001/ajax_demo/dwr-ajax.html來運行程序。 可以查看HTML 源代碼,了解它如何工作。給人印象最深的是,代碼如此簡單——所有與服務(wù)器的交互都隱藏在JavaScript對象ajaxSampleSvc的后面。更加令人驚訝的是,ajaxSampleSvc服務(wù)不是由手工編寫而是完全自動生成的!讓我們繼續(xù),看看這是如何做到的。 引入DWR 如同在“原始的Ajax”一節(jié)所演示的那樣,直接使用XmlHttpRequest創(chuàng)建異步請求非常麻煩。不僅JavaScript代碼冗長,而且必須考慮服務(wù)器端為定位Ajax請求到適當?shù)姆?wù)所需做的工作,并將結(jié)果封送到瀏覽器。 設(shè)計DWR的目的是要處理將Web頁面安裝到后端服務(wù)上所需的所有信息管道。它是一個Java框架,可以很輕松地將它插入到Web應(yīng)用程序中,以便JavaScript代碼可以調(diào)用服務(wù)器上的服務(wù)。它甚至直接與Spring Framework集成,從而允許用戶直接向Web客戶機公開bean。 DWR真正的巧妙之處是,在用戶配置了要向客戶機公開的服務(wù)之后,它使用反射來生成JavaScript對象,以便Web頁面能夠使用這些對象來訪問該服務(wù)。然后Web頁面只需接合到生成的JavaScript對象,就像它們是直接使用服務(wù)一樣;DWR無縫地處理所有有關(guān)Ajax和請求定位的瑣碎細節(jié)。 讓我們仔細分析一下示例代碼,弄清它是如何工作的。 應(yīng)用程序細節(jié):DWR分析 關(guān)于應(yīng)用程序,首先要注意的是,它是一個標準的Java應(yīng)用程序,使用分層架構(gòu)(Layered Architecture)設(shè)計模式。使用DWR通過JavaScript公開一些服務(wù)并不影響您的設(shè)計。
下面是一個簡單的Java服務(wù),我們將使用DWR框架直接將其向JavaScript代碼公開: package com.tearesolutions.service; public interface AjaxSampleSvc { Article castVote(int rank); } 這是一個被簡化到幾乎不可能的程度的例子,其中只有一篇文章可以投票。該服務(wù)由Spring管理,它使用的bean名是ajaxSampleSvc,它的持久性需求則依賴于ArticleDao。詳情請參見applicationContext.xml。 為了把該服務(wù)公開為JavaScript對象,需要配置DWR,添加dwr.xml文件到WEB-INF目錄下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 0.4//EN" "http://www./dwr/dwr.dtd"> <dwr> <allow> <create creator="spring" javascript="ajaxSampleSvc"> <param name="beanName" value="ajaxSampleSvc" /> </create> <convert converter="bean" match="com.tearesolutions.model.Article"/> <exclude method="toString"/> <exclude method="setArticleDao"/> </allow> </dwr> dwr.xml文件告訴DWR哪些服務(wù)是要直接向JavaScript代碼公開的。注意,已經(jīng)要求公開Spring bean ajaxSampleSvc。DWR將自動找到由應(yīng)用程序設(shè)置的SpringApplicationContext。為此,必須使用標準的servlet過濾器ContextLoaderListener來初始化Spring ApplicationContext。 DWR被設(shè)置為一個servlet,所以把它的定義添加到web.xml:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java./dtd/web-app_2_3.dtd"> <web-app> <display-name>Ajax Examples</display-name> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>ajax_sample</servlet-name> <servlet-class>com.tearesolutions.web.AjaxSampleServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet> <servlet-name>dwr-invoker</servlet-name> <display-name>DWR Servlet</display-name> <description>Direct Web Remoter Servlet</description> <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>true</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>ajax_sample</servlet-name> <url-pattern>/ajax_sample</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>dwr-invoker</servlet-name> <url-pattern>/dwr/*</url-pattern> </servlet-mapping> </web-app> 做完這些之后,可以加載http://localhost:7001/ajax-demo/dwr,看看哪些服務(wù)可用。結(jié)果如下: 圖3. 可用的服務(wù) 單擊ajaxSampleSvc鏈接,查看有關(guān)如何在HTML頁面內(nèi)直接使用服務(wù)的示例實現(xiàn)。其中包含的兩個JavaScript文件完成了大部分的功能:<script type=‘text/javascript‘ src=‘/ajax-demo/dwr/interface/ajaxSampleSvc.js‘></script> <script type=‘text/javascript‘ src=‘/ajax-demo/dwr/engine.js‘></script> ajaxSampleSvc.js是動態(tài)生成的: function ajaxSampleSvc() { } ajaxSampleSvc.castVote = function(callback, p0) { DWREngine._execute(callback, ‘/ajax-demo/dwr‘, ‘a(chǎn)jaxSampleSvc‘, ‘castVote‘, p0); } 現(xiàn)在可以使用JavaScript對象ajaxSampleSvc替換所有的XmlHttpRequest代碼,從而重構(gòu)raw-ajax.html文件??梢栽赿wr-ajax.html文件中看到改動的結(jié)果;下面是新的JavaScript函數(shù): function castVote(rank) { ajaxSampleSvc.castVote(processResponse, rank); } function processResponse(data) { var voteText = " 驚人地簡單,不是嗎?由ajaxSampleSvc對象返回的Article域?qū)ο笮蛄谢癁橐粋€JavaScript對象,允許在它上面調(diào)用諸如numberOfVotes()和voteAverage()之類的方法。在動態(tài)生成并插入到DIV元素“votes”中的HTML代碼內(nèi)使用這些數(shù)據(jù)。 下一步工作 在后續(xù)文章中,我將繼續(xù)有關(guān)Ajax的話題,涉及下面這些方面:
像許多技術(shù)一樣,Ajax是一把雙刃劍。對于一些用例,其應(yīng)用程序其實沒有必要使用Ajax,使用了反而有損可用性。我將介紹一些不適合使用的模式,突出說明Ajax的一些消極方面,并展示一些有助于緩和這些消極方面的機制。例如,對Netflix電影瀏覽器來說,Ajax是合適的解決方案嗎?或者,如何提示用戶確實出了一些問題,而再次單擊按鈕也無濟于事?
在使用Ajax時,最初的文檔DOM會發(fā)生一些變化,并且有大量的頁面狀態(tài)信息存儲在客戶端變量中。當用戶跟蹤一個鏈接到應(yīng)用程序中的另一個頁面時,狀態(tài)就丟失了。當用戶按照慣例單擊Back按鈕時,呈現(xiàn)給他們的是緩存中的初始頁面。這會使用戶感到非常迷惑!
使用JavaScript在客戶端執(zhí)行更多的工作時,如果事情不按預(yù)期方式進行,就需要一些調(diào)試工具來幫助弄清出現(xiàn)了什么問題。 結(jié)束語 本文介紹了Ajax方法,并展示了如何使用它來創(chuàng)建一個動態(tài)且響應(yīng)靈敏的Web應(yīng)用程序。通過使用DWR框架,可以輕松地把Ajax融合到站點中,而無需擔(dān)心所有必須執(zhí)行的實際管道工作。 特別感謝Getahead IT咨詢公司的Joe Walker和他的團隊開發(fā)出DWR這樣神奇的工具。感謝你們與世界共享它! 下載 本文中演示的應(yīng)用程序源代碼可供下載:ajax-demo.war(1.52 MB)。 參考資料
原文出處 An Introduction To Ajax |
|