使用JavaScript技術(shù),一個(gè)HTML頁(yè)面可以異步地對服務(wù)器(一般是載入頁(yè)面的服務(wù)器)發(fā)送請求并獲取XML文檔。然后JavaScript可以使用XML文檔來(lái)更新或改動(dòng)HTML頁(yè)面的文檔對象模型(DOM)。最近形成了一個(gè)術(shù)語(yǔ)AJAX(Asynchronous JavaScript Technology and XML)來(lái)描述這種交互模型。
AJAX其實(shí)不是很新的東西。這些技術(shù)對于Windows平臺上專(zhuān)注于Internet Explorer的開(kāi)發(fā)人員來(lái)說(shuō),已經(jīng)存在好幾年了。直到最近,這個(gè)技術(shù)才被作為Web遠程技術(shù)或者遠程腳本技術(shù)被大家了解。Web開(kāi)發(fā)人員也有一段時(shí)間曾經(jīng)使用過(guò)插件、Java applet和隱藏框架來(lái)模擬這種交互模型。最近發(fā)生的變化是,對XMLHttpRequest對象的支持已經(jīng)成為所有平臺上的主流瀏覽器都包括的特性了。JavaScript技術(shù)的XMLHttpRequest對象是。盡管在正式的JavaScript技術(shù)標準中并沒(méi)有提到這種對象,然而今天主流的瀏覽器都對他提供了支持。而當代的瀏覽器如Firefox、Internet Explorer以及Safari在JavaScript技術(shù)和CSS的支持上有些細微的差別,但是這種差別是可以處理的。如果你要考慮支持較老的瀏覽器,AJAX也許就不能成為你的解決方法。
基于A(yíng)JAX的客戶(hù)端之所以獨特的原因是客戶(hù)端包含了用JavaScript嵌入的特定于頁(yè)面的控制邏輯。應用JavaScript技術(shù)的頁(yè)面基于事件進(jìn)行交互,如文檔載入、鼠標點(diǎn)擊、焦點(diǎn)改變甚至是定時(shí)器。AJAX交互使得表現層邏輯更加清晰地與數據分離。一個(gè)HTML頁(yè)面也可以根據需要每次讀入適當的數據,而不是每次需要顯示一個(gè)更改時(shí)都重新載入整個(gè)頁(yè)面。AJAX要求一種不同的服務(wù)器架構來(lái)支持它這種交互模型。以前,服務(wù)器端Web應用關(guān)注于對每個(gè)導致服務(wù)器調用的客戶(hù)端事件都生成HTML文檔。然后客戶(hù)端對每個(gè)回應都要重新讀入并重新渲染完整的HTML頁(yè)面。富Web應用(Rich Web Application)關(guān)注于,讓一個(gè)客戶(hù)端獲取一個(gè)HTML文檔讓它表現為一個(gè)模板或者是一個(gè)容器,可以基于事件并使用從服務(wù)器端組件中獲取的XML 數據來(lái)對文檔注入內容。
一些AJAX交互的應用如:
這個(gè)列表并未把所有的應用都列出來(lái),但它已經(jīng)顯示了AJAX交互可以讓W(xué)eb應用比從前能做更多的事情。但盡管這些好處是值得關(guān)注的,這種方式也有一些缺點(diǎn):
XMLHttpRequest對象的標準化:XMLHttpRequest對象還不是JavaScript技術(shù)標準的一部分,這就意味著(zhù)根據客戶(hù)端的不同,應用的行為也有所會(huì )不同。
當開(kāi)發(fā)人員在使用AJAX交互模型上獲得更多的經(jīng)驗后,AJAX技術(shù)的框架和模式就會(huì )慢慢浮現出來(lái)?,F在就關(guān)注于完全通用的AJAX交互框架,還為時(shí)過(guò)早。本文和相關(guān)的解決方案將關(guān)注于在現有的Java 2平臺企業(yè)版(J2EE)上如何對AJAX進(jìn)行支持,像servlet,JavaServer Page(JSP)軟件、JavaServer Face應用和Java標準標簽庫(JSTL)。
現在我們已經(jīng)討論了AJAX是什么以及一些高層次的問(wèn)題。那現在讓我們把所有的零件放在一起來(lái)展示一個(gè)具有AJAX的J2EE應用。
首先考慮一個(gè)例子。一個(gè)Web應用包括了一個(gè)靜態(tài)HTML頁(yè)面,或者是一個(gè)由JSP生成的HTML頁(yè)面,這個(gè)JSP中還包括了一個(gè)HTML表單,它需要服務(wù)器端邏輯來(lái)對表單中的數據進(jìn)行檢驗,而不用刷新頁(yè)面。一個(gè)名為ValidateServlet服務(wù)器端組件(servlet)用來(lái)提供這種驗證邏輯。圖一描述了這種具有驗證邏輯的AJAX交互的細節。
|
|
圖1: 一個(gè)提供驗證邏輯的AJAX交互
|
以下條目代表了圖1中出來(lái)AJAX交互的過(guò)程:
XMLHttpRequest對象。
XMLHttpRequest對象進(jìn)行一個(gè)調用。
ValidateServlet對請求進(jìn)行處理。
ValidateServlet返回一個(gè)包含了結果的XML文檔。
XMLHttpRequest對象調用callback()函數并處理結果。
現在讓我們逐個(gè)研究這個(gè)AJAX模型的每一步。
在一個(gè)事件發(fā)生時(shí)可以調用相應的JavaScript函數。在這里,validate()函數可以被映射到一個(gè)鏈接或者是表單組件的onkeyup事件上去。
<input type="text"
size="20"
id="userid"
name="id"
onkeyup="validate();">
|
每次用戶(hù)在表單域中按下一個(gè)鍵時(shí),表單元素將都調用validate()函數。
XMLHttpRequest對象創(chuàng )建和配置一個(gè)XMLHttpRequest對象
var req; function validate() { var idField = document.getElementById("idField"); var url = "validate?id=" + escape(idField.value); if (window.XMLHttpRequest) { req = new XMLHttpRequest(); } else if (window.ActiveXObject) { req = new ActiveXObject("Microsoft.XMLHTTP"); } req.open("GET", url, true); req.onreadystatechange = callback; req.send(null); } |
validate()函數建立了一個(gè)XMLHttpRequest對象并對象中的open函數。open函數需要兩個(gè)參數:HTTP方法,可以是GET或POST; 和對象進(jìn)行交互的服務(wù)器端組件的URL;一個(gè)布爾變量,表示是否要進(jìn)行異步調用。API是XMLHttpRequest.open(String method, String URL, boolean asynchronous)。如果一個(gè)交互被設置為異步, (true) 那就必須指明一個(gè)回調函數??梢允褂?code>req.onreadystatechange = callback;來(lái)設置這個(gè)交互的回調函數。詳細內容見(jiàn)第六節。
XMLHttpRequest對象進(jìn)行調用當收到了語(yǔ)句req.send(null);,就會(huì )進(jìn)行一次調用。HTTPGET的情況下,內容可以是null或者留空。當調用XMLHttpRequest的這個(gè)函數時(shí),也會(huì )對已經(jīng)配置了的URL進(jìn)行調用。在下面這個(gè)例子中,要發(fā)送的數據(id)將作為一個(gè)URL參數。
使用HTTPGET,兩個(gè)重復的請求將返回同樣的結果。當使用HTTPGET方法時(shí),要注意URL的長(cháng)度,包括已經(jīng)轉義的URL參數,可能會(huì )受到某些瀏覽器和服務(wù)器端的Web容器的限制。當發(fā)送的數據會(huì )影響到服務(wù)器端的應用程序的狀態(tài)時(shí),就應該使用HTTPPOST方法。使用HTTPPOST必須要對XMLHttpRequest對象設置一個(gè)Content-Type頭,使用以下語(yǔ)句:
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); req.send("id=" + escape(idTextField.value)); |
當從JavaScript中發(fā)送表單值得時(shí)候,你應該考慮對字段值進(jìn)行編碼。JavaScript中有一個(gè)函數escape(),應該用他來(lái)確保區域化的內容被正確編碼,同時(shí)特殊字符也被正確轉義。
ValidateServlet對請求進(jìn)行處理.一個(gè)映射到URI "validate" 的servlet將檢驗user ID是不是已經(jīng)在數據庫中存在了。
一個(gè)servlet處理一個(gè)XMLHttpRequest ,就像對待其它的HTTP請求一樣。下面的例子顯示了服務(wù)器從請求中抽取出id參數并檢驗是否被占用了。
public class ValidateServlet extends HttpServlet { private ServletContext context; private HashMap users = new HashMap(); public void init(ServletConfig config) throws ServletException { this.context = config.getServletContext(); users.put("greg","account data"); users.put("duke","account data"); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String targetId = request.getParameter("id"); if ((targetId != null) && !users.containsKey(targetId.trim())) { response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); response.getWriter().write("<message>valid</message>"); } else { response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); response.getWriter().write("<message>invalid</message>"); } } } |
在這個(gè)例子中,一個(gè)簡(jiǎn)單的HashMap用來(lái)存放存在的用戶(hù)名。在這個(gè)例子中,我們假設用戶(hù)的ID是duke。
ValidateServlet返回一個(gè)包含結果的XML文檔用戶(hù)ID "duke" 在users HashMap的用戶(hù)ID列表中出現了。將在應答中寫(xiě)一個(gè)包含值為invalid的message元素的XML文檔。更復雜的用例將要求DOM、XSLT或其他API來(lái)生成這個(gè)應答。
response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); response.getWriter().write("<message>invalid</message>"); |
開(kāi)發(fā)人員必須注意兩個(gè)事情。第一,Content-Type必須設為text/xml。第二,Cache-Control必須設為no-cache。XMLHttpRequest對象只會(huì )處理Content-Type為text/xml的應答,同時(shí)把將Cache-Control設為no-cache將確保瀏覽器不會(huì )從緩存相同的URL(包括參數)返回的應答。
XMLHttpRequest對象調用callback()函數并處理結果。XMLHttpRequest對象已經(jīng)配置為當有readyState改變的時(shí)候就調用callback()函數。讓我們假設已經(jīng)ValidateServlet調用了而且ValidateServlet是4,表示XMLHttpRequest的調用已經(jīng)完成。HTTP狀態(tài)代碼200表示一個(gè)成功的HTTP交互。
function callback() { if (req.readyState == 4) { if (req.status == 200) { // update the HTML DOM based on whether or not message is valid } } } |
瀏覽器維護了一個(gè)所顯示的文檔的對象形式(也就是所謂的Docuemt Object Model或DOM)。HTML頁(yè)面中的JavaScript可以訪(fǎng)問(wèn)DOM,同時(shí)在頁(yè)面載入完之后,可以使用API來(lái)修改DOM。
根據成功的請求,JavaScript代碼可以修改HTML頁(yè)面的DOM。從ValidateServlet獲得的對象形式的XML文檔可以通過(guò)req.responseXML在JavaScript中獲得,req是一個(gè)XMLHttpRequest對象。DOM API給JavaScript提供了獲取這個(gè)文檔中的內容以及修改HTML頁(yè)面的DOM的方法。所返回的字符串形式的XML文檔可以通過(guò)req.responseText獲得?,F在我們看看如何在JavaScript中使用DOM API,先看以下從ValidateServlet返回的XML文檔。
<message> valid </message> |
這個(gè)例子是一個(gè)簡(jiǎn)單的只包含了一個(gè)message元素的XML片斷,里面只有一個(gè)簡(jiǎn)單的字符串valid或invalid。一個(gè)更高級的例子可以包含多于一個(gè)的消息和可以給用戶(hù)看的有效的名字:
function parseMessage() { var message = req.responseXML.getElementsByTagName("message")[0]; setMessage(message.childNodes[0].nodeValue); } |
parseMessages()函數將處理一個(gè)從ValidateServlet獲取的XML文檔。這個(gè)函數會(huì )調用setMessage()with the,并給出message作為參數來(lái)更新HTML DOM。
JavaScript技術(shù)可以使用很多API從HTML DOM中獲得任何元素對象的引用。推薦的獲得元素引用的方法是調用document.getElementById("userIdMessage"), "userIdMessage"是HTML文檔中出現的一個(gè)元素的ID屬性。有了這個(gè)元素的引用,就可以使用JavaScript來(lái)修改元素的屬性、修改元素的樣式、添加、刪除或修改子元素。
一個(gè)常見(jiàn)的改變元素主體內容的方法是設置元素的innerHTML屬性,如下所示:
<script type="text/javascript">
function setMessage(message) {
var userMessageElement = document.getElementById("userIdMessage");
userMessageElement.innerHTML = "<font color=\"red\">" + message + " </font>";
}
</script>
<body>
<div id="userIdMessage"></div>
</body>
|
受到影響的那部分HTML頁(yè)面會(huì )立刻根據innerHTML的設置重新渲染。如果innerHTML屬性包含類(lèi)似<image>或者是<iframe>之類(lèi)的元素,那么由那些元素所指定的內容同樣會(huì )被獲取并渲染。
這種途徑的主要缺點(diǎn)是HTML元素是作為字符串硬編碼在JavaScript中的。JavaScript中硬編碼的HTML標記不是一種好的實(shí)踐,因為它使代碼難于閱讀、維護和修改。我們應該考慮在JavaScript中使用DOM API來(lái)創(chuàng )建和修改HTML元素。把顯示和JavaScript代碼的字符串混在一起只會(huì )讓頁(yè)面更難于閱讀和編輯。
另一種修改HTML DOM的方法是動(dòng)態(tài)地產(chǎn)生新的元素并把他們作為子元素追加到目標元素,如下面的例子所示:
<script type="text/javascript">
function setMessage(message) {
var userMessageElement = document.getElementById("userIdMessage");
var userIdMessageFont = document.getElementById("userIdMessageFont");
var messageElement = document.createTextNode(message);
if (userMessageElement.childNodes[0]) {
// 更新元素
userIdMessageFont.replaceChild(messageElement, userIdMessageFont.childNodes[0]);
} else {
// 建立一個(gè)新的元素
var fontElement = document.createTextNode("font");
fontElement.setAtribute("id", "userIdMessageFont");
fontElement.setAtribute("color", "red");
userMessageElement.appendChild(fontElement);
fontElement.appendChild(messageElement);
}
}
</script>
<body>
<div id="userIdMessage"></div>
</body>
|
這個(gè)范例展示了JavaScript技術(shù)的DOM API可以用來(lái)更有目的地建立或改變一個(gè)元素。當然JavaScript的DOM AP在不同的瀏覽器上也可能有差別,所以你必須在開(kāi)發(fā)應用程序時(shí)小心。
TheJava Blueprints Solutions Catalog是用來(lái)收集J2EE技術(shù)上AJAX的最佳實(shí)踐的。每個(gè)解決方案包含一個(gè)問(wèn)題和方法的描述、一個(gè)設計文檔和可運行的源碼。這些解決方案是為了讓你根據需要在自己的應用程序中復用。以下是已經(jīng)提供的AJAX交互:
自動(dòng)補全提供了當用戶(hù)在一個(gè)HTML表單中輸入一個(gè)請求時(shí)對數據瀏覽的簡(jiǎn)化方式。當用戶(hù)面對一大片數據時(shí),可以在輸入數據時(shí)把可能的完整形式顯示給用戶(hù)。然后選擇其中一個(gè)完整形式可以保證用戶(hù)輸入的數據已經(jīng)存在在服務(wù)器上。
考慮一個(gè)大公司的一個(gè)名字查找的Web應用。如圖2所示,只要輸入姓或名的開(kāi)頭幾個(gè)字母就可以得到人的列表。用戶(hù)可以然后就只要點(diǎn)擊一下就可以瀏覽用戶(hù)的詳細信息。
|
|
圖2:名字自動(dòng)補全
|
在Web應用中,一個(gè)服務(wù)器端任務(wù)也可能要花一段時(shí)間去完成。這段時(shí)間很可能會(huì )超過(guò)HTTP交互的時(shí)間上限(超時(shí))。當用戶(hù)不知道這個(gè)任務(wù)什么時(shí)候才能完成時(shí),用戶(hù)很可能會(huì )重新提交一次表單或直接退出會(huì )話(huà)狀態(tài)。一般來(lái)說(shuō),Web應用使用頁(yè)面刷新來(lái)跟蹤服務(wù)器端操作的狀態(tài),這種方式可能會(huì )讓人厭煩而且也不準確。AJAX可以用來(lái)僅在一個(gè)HTML頁(yè)面中跟蹤服務(wù)器端操作的狀態(tài)而無(wú)需刷新頁(yè)面。用戶(hù)可以以圖形方式看到服務(wù)器端操作的進(jìn)度,如圖3。
![]() |
|
圖3:進(jìn)度條
|
向一個(gè)HTML頁(yè)面提供最新的數據或服務(wù)器消息提醒在現在的Web世界中也是十分重要的,因為現在的Web世界中數據一直不停變化。盡管它不是一個(gè)實(shí)實(shí)在在的推送技術(shù),但它可以通過(guò)使用AJAX交互不斷進(jìn)行查詢(xún)來(lái)模擬。當數據需要更新或者要進(jìn)行提醒,HTML頁(yè)面將會(huì )動(dòng)態(tài)地改變。圖4顯示了HTML頁(yè)面中的一個(gè)服務(wù)器端計數器。這個(gè)計數器會(huì )在頁(yè)面后臺自動(dòng)更新。
![]() |
|
圖4:服務(wù)器端計數器在刷新數據
|
不是所有的表單域都可以單獨用JavaScript技術(shù)在客戶(hù)端完成。某些表單數據要求服務(wù)器端的驗證邏輯。傳統和Web應用曾使用頁(yè)面刷新來(lái)完成這種驗證,但這可能有些讓人煩。
考慮一個(gè)需要一個(gè)唯一用戶(hù)ID的Web應用。使用AJAX交互,用戶(hù)可以在輸入的時(shí)候就知道ID是否有效(圖5)。
![]() |
|
圖5:指出用戶(hù)ID無(wú)效
|
當一個(gè)用戶(hù)輸入了一個(gè)無(wú)效的用戶(hù)ID,應用程序禁止了提交按鈕并且向用戶(hù)顯示了一個(gè)信息(圖6)。
![]() |
|
圖6:用戶(hù)ID通過(guò)驗證
|
用戶(hù)馬上就能知道用戶(hù)ID是可用的也是有效的。
我們已經(jīng)看到AJAX交互可以解決很多問(wèn)題。配合HTTP處理、數據庫、Web服務(wù)、XML處理和業(yè)務(wù)對象等API,J2EE技術(shù)已經(jīng)提供了一個(gè)開(kāi)發(fā)和部屬基于A(yíng)JAX應用的一個(gè)良好的基礎。有了對于這個(gè)交互模型的更好的理解,今天的應用程序可以變得更加有交互性,給最終用戶(hù)更好的體驗。
使用AJAX要求你使用支持XMLHttpRequest對象的最新瀏覽器版本。使用AJAX還要求大量對JavaScript技術(shù)和CSS的應用。作為一個(gè)應用程序架構師或是一個(gè)開(kāi)發(fā)人員,你要會(huì )針對瀏覽器支持、架構復雜度和對開(kāi)發(fā)人員的培訓等方面來(lái)衡量開(kāi)發(fā)一個(gè)富應用的需要。當AJAX編程模型不斷地發(fā)展,現有的技術(shù)和框架會(huì )讓這種轉變更加容易。
很明顯的是,突出的Web應用都越來(lái)越有交互性了。那么你的呢?
XMLHttpRequest對象的不錯的文檔。
Greg Murray 是is a Sun Microsystems 的一名工程師,是servlet標準的領(lǐng)導人,BluePrint小組的前成員,在這個(gè)小組時(shí)他已經(jīng)開(kāi)始關(guān)注Web層次問(wèn)題。他也是《Enterprise Applications With the Java 2 Platform,Enterprise Edition和Designing Web Services With the J2EE 1.4 Platform(Addison-Wesley)》一書(shū)的協(xié)助編撰者。
聯(lián)系客服