原文:https://blueprints.dev.java.net/ajax-faq.html
現在我們都有機會用AJAX 了,好多人發現這是個全新的世界。許多開發員會通過現有的框架來接觸到AJAX ,或許你想深入鉆研,擴充現有的功能。這些常見問題就是針對想在應用內加上AJAX 功能的開發員的。
AJAX現在當然很紅,但它不一定適合你。AJAX只能用在最新的瀏覽器上,暴露出許多瀏覽器兼容性問題,對許多人它還要求學會一些新技術。Alex Bosworth 寫了篇不錯的網志 AJAX Mistakes,在你全力投入AJAX以前值得一看。
另一方面,你可以實現豐富的,高度交互性的,快速響應的WEB應用,它們看起來真的很快。盡管人們還在爭論基于AJAX的應用是不是真的更快, 用戶已感到更直接了,因為當數據在后臺交換的時候,用戶就收到了積極反饋。如果你較早采用了AJAX,能夠處理瀏覽器兼容性問題,而且愿意學習一些新技術,那么AJAX是適合你的。謹慎起見,你可以試著把應用的一小部分或者小組件轉化成AJAX。我們都喜歡新技術,但要記住AJAX的目的是要改善而不是妨礙用戶的體驗。
當然。Java很適合AJAX!你可以用Java企業版服務器來生成AJAX 客戶端頁面并處理進來的請求,為AJAX 客戶端管理服務器端的狀態,并且把AJAX 客戶端聯接到企業資源。JavaServer Faces 組件模型非常適合于定義和使用AJAX組件。
如果你還沒有選好一個框架,我建議你考慮使用JavaServer Faces 或者基于JavaServer Faces的框架。你可以用JavaServer Faces組件來抽象化處理許多細節:生成JavaScript, AJAX交互, 和DHTML處理,這樣JSF開發員就可以更方便地使用AJAX,它們還可以作為插件裝入支持JSF的IDE內,比如 Sun Java Studio Creator.
如果你現在的框架不能滿足你的用例,你想開發自己的AJAX組件和功能,我建議你先看一下這篇文章Asynchronous JavaScript Technology and XML (AJAX) With Java 2 Platform, Enterprise Edition。(譯者注:中文版 在Java2平臺企業版中應用異步 JavaScript技術和XML(AJAX))
如果你想看一個很基本的例子和源代碼,這里有 Using AJAX with Java Technology。 Blueprints AJAX Home 有更完整的AJAX資源。
接下來,我建議你花些時間研究AJAX庫和框架。如果你想自己寫AJAX客戶端腳本,最好不要再重復勞動了。
Dave Crane,Eric Pascarello和Darren James的AJAX in Action也不錯。這本書有附錄中講學習 JavaScript,對Java開發員有幫助。
如果你不想利用已有的AJAX組件,這里是一些需要注意的地方。
準備學習動態HTML (DHTML), AJAX的基礎技術。DHTML讓用戶與網頁間通過瀏覽器現時交互成為可能。DHTML結合了JavaScript,文檔對象模型(DOM)和層疊樣式表 (CSS).
理解HTTP的請求/回應這一基本性質也很重要。如果你在配置XMLHttpRequest時忽略了GET和POST方法的區別,或者在處理回調時忽略了HTTP狀態代碼,你會碰到許多難題。
從某種意義上說,JavaScript是一種客戶端的膠合體。JavaScript被用來創建XMLHttpRequest對象并觸發異步調用。 JavaScript被用來解析返回的內容。JavaScript被用來分析返回的數據并處理返回的信息。用JavaScript可以通過DOM API往HTML里注入內容和改變CSS。
一般來說,是的,如果你想為你的WEB應用開發新的AJAX功能。
另外一方面, JSF 組件和組件庫能抽象化JavaScript,DOM和CSS的細節。這些組件能生成必要的工件(artifacts)來支持AJAX交互。可視的工具,象Java Studio Creator,也可以利用支持AJAX的JSF組件來創建應用,應用開發者就不用擔心AJAX的許多細節了。如果你打算寫自己的 JSF組件,或者想把組件間的事件串聯在一起,你應該對JavaScript有個基本了解。你可以從你頁面里的JavaScript調用一些客戶端的JavaScript 庫(在下面談到)來抽象化測聲器間的差異。 Object Hierarchy and Inheritance in JavaScript 是一個供Java開發員學習JavaScript對象的很好的參考。
有好多新的JavaScript庫正在涌現出來,以上只是回顧了比較常見的庫。選擇最適合你需要的。盡管你最好用一個框架,當然也可以同時用多個。更詳細的客戶端框架列單,請看:Survey of AJAX/JavaScript Libraries.
這要看情況。很清楚,AJAX里的X代表了XML,但是一些AJAX支持者馬上也指出,AJAX本身并不排除使用其它種類做載荷(PAYLOAD),比如,JavaScript, HTML, 或是純文本格式。
eval()函數來創建這些對象。
JSON,一個基于 JavaScript對象的數據交換規范,就是依賴了這種技術。
網絡聚合(Mashup) 這是一個時下流行的術語,它從離散的WEB SERVICES和其它在線API獲取內容,結合在一起創建全新的WEB應用。 一個很好的網絡聚合的例子就是housingmaps.com,它把craiglist.org 的住房廣告和 maps.google.com的地圖結合在一起。
通過AJAX交互和DHTML的途徑來獲取數據,動態更新頁面,這在本質上是會大幅度地改變頁面的外觀和狀態。用戶可能會在任何時候用到瀏覽器的向前或退后按鈕,書簽收藏當前頁面,從地址欄復制URL后通過電子郵件或聊天發給朋友,或者是打印頁面。在設計AJAX應用時,你應該考慮在發生這些情況時你預期的結果是怎樣:導航,書簽收藏,打印,瀏覽器支持, 如下所述:
開發員使用AJAX時其它考慮因素:
降解效果(Degradability)這個術語描述了WEB應用用于適應廣泛的瀏覽器功能的一系列技巧。許多AJAX庫有內置的自動降解方式。如果你要寫自己的AJAX功能,考慮采納一些標準組織,比如World Wide Web Conrsoritum (W3C), 和草根運動,比如Web Standards和其它人的最佳實踐。這樣你的應用在不支持AJAX的瀏覽器上仍然能運行,雖然它失去了一些很搶眼的效果。
記住不要只是為了顯得酷而用AJAX。你打造WEB應用是為了給人用的。如果和他們的瀏覽器上不兼容,用戶就不會用你的應用。
沒有很多調試器能同時支持客戶端和服務器端調試。我肯定隨著AJAX的普及,這種情況會改變。我現在分開調試客戶端和服務器。以下是一些關于常見瀏覽器上的客戶端調試器的情況:
除了調試器能幫忙,還有一種常用的方法叫“對話框調試(Alert Debugging)”。在這種情況下你加入"alert()"函數調用,就好比在JAVA中用System.out.println一樣。盡管是一個很小的竅門,對大部分情況都管用。有些框架,象Dojo,提供 API來追蹤調試語句。
如果相對于一個待定的URL的數據一會改變,那么AJAX請求應該用HTTP GET方法。如果服務器會更新狀態,那么就應該用HTTP POST方法。這和HTTP idempotency recommendations的要求是一致的,也是大家為創造一個統一的WEB應用設計而高度推薦的做法。
不能認為用了XML, 你就能以AJAX請求發送和獲取區域化的內容。要提供國際化的AJAX組件,你需要做以下這些要求:
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
escape()函數來返回統一碼(UNICODE)轉義字符串,這樣區域化的文本就以
十六進制的格式出現。更多關于JavaScript編碼的信息,請看Comparing escape(), encodeURI(), and encodeURIComponent(). request.setCharactherEncoding("UTF-8");
. 返回AJAX回應的服務器端組件需要把回應的編碼設成和該頁面一樣的編碼。
response.setContentType("text/xml;charset=;UTF-8");
response.getWriter().write("<response>invalid</response>");
關于在Java企業版上使用AJAX,更多資料請看AJAX and Internationalization,開發多種語言應用,請看Developing Multilingual Web Applications Using JavaServer Pages Technology.
通過JavaScript你可以同時處理多個AJAX 請求。為了確保妥善的后處理,我建議使用JavaScript閉包(JavaScript Closures)。下面這個例子顯示了一個XMLHttpRequest 被抽象化為JavaScript對象AJAXInteraction
。傳進的參數是要調用的URL和處理結束后要調用的函數。
function AJAXInteraction(url, callback) {
var req = init();
req.onreadystatechange = processRequest;
function init() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else if (window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
function processRequest () {
if (req.readyState == 4) {
if (req.status == 200) {
if (callback) callback(req.responseXML);
}
}
}
this.doGet = function() {
req.open("GET", url, true);
req.send(null);
}
this.doPost = function(body) {
req.open("POST", url, true);
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
req.send(body);
}
}
function makeRequest() {
var ai = new AJAXInteraction("processme", function() { alert("Doing Post Process");});
ai.doGet();
}
在上例中,makeRequest()函數創建了一個
AJAXInteraction
,傳入兩個參數:要調用的URL“processme”和一個內聯函數。這個內聯函數會彈出對話框來顯示信息"Doing Post Process"。調用ai.doGet()
就啟動了AJAX 互動,當映射到URL "processme" 的服務器端組件返回一個文檔,這個文檔再被傳給創建這個
AJAXInteraction時指定的回調函數。
使用這樣的閉包能確保對應于一特定的AJAX交互的回調函數能被正確調用。當你使用多個閉包對象來做XmlHttpRequests時仍需小心,因為在任何一特定時刻都只有有限的套接字(SOCKET)用來發出請求。因為只能有有限數目的并行請求,Internet Explorer只允許任何時刻最多兩個并行AJAX請求 。其它瀏覽器可能會允許更多,但一般是三個到五個之間。你也可以用池來管理AJAXInteraction對象。
有一點要注意,當從客戶端發出多個AJAX請求時,它們的返回值沒有特定的次序。在回調函數中使用閉包能確保這些信賴能被正確處理。
這個討論很有幫助Ajaxian Fire and Forget Pattern。
對于返回值類型XML,"Content-Type" 要設成"text/xml"。在SERVLET里可以用 HttpServletReqponse.setContentType()
。許多XMLHttpRequest 實現如果發現"Content-Type"已經設了會報錯。一面的代碼顯示了如何設"Content-Type":
response.setContentType("text/xml");
response.getWriter().write("<response>invalid</response>");
你可能要考慮是不是要設緩存標頭。在些時候,比如自動補全,你可能要通知代理服務器和瀏覽器不要緩存結果。
response.setContentType("text/xml");開發員注意:如果這個標頭沒有設,Internet Explorer 對于HTTP GET方法的響應會自動用緩存的結果。這可能會引起一些問題。所以在開發的時候你可能要設這個標頭。
response.setHeader("Cache-Control", "no-cache");
response.getWriter().write("<response>invalid</response>");
和任何基于瀏覽器的WEB應用一樣,你有以下這些選擇:
當建立表單的時候,要確保"form"元素的"onSubmit"屬性設成一個返回false的JavaScript函數。
<form onSubmit="doAJAXSubmit();return false;" >
<input type="text" id="tf1" />
<input type="submit" id="submit1" value="Update"/>
</>
用同樣的方法,你也可以聯接函數和表單按鈕,從而來提交表單:
<form onSubmit="doAJAXSubmit();return false;" >
<input type="text" id="tf1" />
<input type="button" id="button1" onClick="doAJAXSubmit()" value="Update"/>
</>
注意:表單上的"onSubmit"屬性已被設置。如果用戶在文本輸入框中按了回車,表單也會被提交。所以你要處理這種情況。
當更新頁面時,我建議你等到確保AJAX已經順利更新了表單數據后再更新頁面數據。否則,數據可能沒有適當更新,用戶卻不知道。我喜歡在做部分更新時給用戶一個提醒信息,等AJAX交互順利結束后,再更新頁面。
這要看情況而定。對于AJAX來說,答案在兩者中間。服務器端組件可實施更強的中央控制,或者服務器端和客戶端一起來控制。
取決于你想實現什么,這兩種方法都可以采用。我趨向于讓把控制分布到服務器端和客戶端。
用戶通過查看頁面源代碼就可以看見JavaScript的源代碼。沒有用戶賦予的權限,JavaScript不能訪問本地文件系統。AJAX只能與提供該頁面下載的服務器上的組件交互。代理模式可用來與外部服務交互。
你要小心有要暴露你應用的模型,因為有些惡意用戶會用逆反工程危及你服務器的組件。就象其它WEB應用一樣,當交換敏感信息的時候,考慮使用 HTTPS來保護連接。
問得好。叫它AJAX是有道理的。一個同步請求會堵塞頁面的事件處理,我還沒發現很多同步比異步好的用例。
不要急于拋棄基于 applet 和 plugin 的應用。盡管AJAX 和 DHTML 能夠實現拖放效果和其它高級用戶界面功能,但它們仍然有局限性,特別是在瀏覽器支持方面。Plugin 和 applet 已經存在了一段時間,而且一直都能實現類似于AJAX 請求的功能。Applet 提供了開發員所需要的一系列優秀的用戶界面組件和API。
許多人不愿意用 applet 或者 plugin,因為它們啟動的時候需要等一段時間,而且無法保證應用需要的JVM plugin 版本已被安裝。Plugin 和 applet 可能在操縱 DOM 上有欠缺。如果你在一個統一的環境下,或者你可以肯定你的應用能找到一個特定版本的JVM plugin(比如在一個公司內部環境),那么 plugin 或 applet 就一失為一個良好的解決方案。
還有一種選擇是把AJAX和 applet 或? plugin 混合起來使用。Flickr 就是這樣,一方面用 AJAX 交互或者 DHTML 來標識圖形和與用戶交流,另一方面用一個 plugin 來管理照片,從面提供了極好的用戶體驗。如果服務器端組件設計得好的話,它們可以同時與這兩種客戶會話。
你可以開發自己的方案在應用中追蹤當前的狀態,但是我建議你還是把這種任務留給專家來做。Dojo 以不待定于瀏覽器的方式來處理導航問題,如下所示:
function updateOnServer(oldId, oldValue, itemId, itemValue) {
var bindArgs = {
url: "faces/ajax-dlabel-update",
method: "post",
content: {"component-id": itemId, "component-value": itemValue},
mimetype: "text/xml",
load: function(type, data) {
processUpdateResponse(data);
},
backButton: function() {
alert("old itemid was " + oldId);
},
forwardButton: function(){
alert("forward we must go!");
}
};
dojo.io.bind(bindArgs);
}
上面的例子用到了 dojo.io.bind() 來更改服務端的一個值,dojo.io.bind()? 還用到了一個函數來負責處理瀏覽器向后按鈕事件。這樣作為一個開發員你就可以在上例中恢復以前的值(oldValue),或者采取其它合適的行動。開發員不用關注怎樣察覺到瀏覽器按鈕事件這些底層細節,因為Dojo已經處理掉了。
AJAX: How to Handle Bookmarks and Back Buttons 這篇文章詳細介紹了這個問題,并提供了一個專門注重向前和后退問題的 JavaScript 庫。
當類似 Google Maps 的應用使用AJAX時,它們看上去似乎在發送圖像,事實上,針對AJAX請求,圖像URL作為響應被發送回去,然后通過DHTML來設置顯示圖像。
在這個例子中,AJAX 交互返回了一個XML 文檔,然后填充 category 一欄。
<categories>
<category>
<cat-id>1</cat-id>
<name>Books</name>
<description>Fun to read</description>
<image-url>books_icon.gif</image-url>
</category>
<category>
<cat-id>2</cat-id>
<name>Electronics</name>
<description>Must have gadgets</description>
<image-url>electronics.gif</image-url>
</category>
</categories>
請注意image-url元素包含了代表
category元素的圖像的URL。AJAX交互的回調函數會解析作為響應的 XML文檔,并針對包括在XML響應文檔中的每個category調用addCategory函數。
addCategory函數在頁面主體中查找一個叫
"categoryTable" 的表格行元素,把以上的圖像作為新的一行加進去。
<scrip type="text/javascript" >
...
function addCategory(id, name, imageSrc) {
var categoryTable = document.getElementById("categoryTable");
var row = document.createElement("tr");
var catCell = document.createElement("td");
var img = document.createElement("img");
img.src = ("images\\" + imageSrc);
var link = document.createElement("a");
link.className ="category";
link.appendChild(document.createTextNode(name));
link.setAttribute("onclick", "catalog?command=category&catid=" + id);
catCell.appendChild(img);
catCell.appendChild(link);
row.appendChild(catCell);
categoryTable.appendChild(row);
}
</script>
...
<table>
<tr>
<td width="300" bgoclor="lightGray">
<table id="categoryTable" border="0" cellpadding="0"></table>
</td>
<td id="body" width="100%">Body Here</td>
</tr>
</table>
請注意到image source元素被設成了圖像的來源。接下來當img元素被加到categoryTable
時,頁面會發出一個HTTP請求來載入位于"images/books_icon.gif" 或 "images/electronic_icon.gif" 的圖像。
JavaScript 沒有線程。當頁面叫發生某種事件時,比如頁面載入,鼠標點擊,或表單元素獲取輸入焦點, JavaScript 的函數會被調用。你可以用setTimeout
來建立一個定時器,這個函數用到兩個參數:另外一個函數的名字和毫秒數。這樣你就可以循環調用同一個函數,如下所示:
function checkForMessage() {
// start AJAX interaction with processCallback as the callback function
}
// callback for the request
function processCallback() {
// do post processing
setTimeout("checkForMessage()", 10000);
}
請注意checkForMessage會一直無限制循環下去。根據你頁面的活動或用例,你可能需要改變一下間隔時間。你可能需要針對處理AJAX響應的某些條件來安排邏輯中斷循環。
【導讀】DWR和Buffalo都是Web Remoting框架,區別在于DWR使用自定義的簡單文本協議,而Buffalo使用burlap協議;DWR的服務器端實現要比Buffalo完善一些…… |
AJAX框架
DWR和Buffalo都是Web Remoting框架,區別在于: