在Java2平臺企業版中應用異步JavaScript技術和XML(AJAX)
在Java2平臺企業版中應用異步JavaScript技術和XML(AJAX)
任何試過過Flickr、GMail、Google Suggest或者是Google
Maps的人都會意識到一種新型的動態Web應用正在逐漸浮出水面。這些應用外觀和表現都和傳統的桌面應用程序很像,而他們不需要依賴于插件或者是特定于
瀏覽器的功能。過去Web應用只是一系列HTML頁面,他們任意一部份內容的更改都必須重新載入頁面。像JavaScript編程語言和層疊樣式表
(CSS)之類的技術已經成熟,可以有效地應用他們來創建高動態的Web應用,而且可以運行在所有的主流瀏覽器中。本文將會詳細介紹你馬上就可以使用的一
些技術,讓他們使你的Web應用像桌面應用更加豐富和更有交互性。
介紹異步JavaScript技術和XML(AJAX)
使用JavaScript技術,一個HTML頁面可以異步地對服務器(一般是載入頁面的服務器)發送請求并獲取XML文檔。然后JavaScript可以
使用XML文檔來更新或改動HTML頁面的文檔對象模型(DOM)。最近形成了一個術語
AJAX
(Asynchronous
JavaScript Technology and XML)來描述這種交互模型。
AJAX其實不是很新的東西。這些技術對于Windows平臺上專注于Internet
Explorer的開發人員來說,已經存在好幾年了。直到最近,這個技術才被作為Web遠程技術或者遠程腳本技術被大家了解。Web開發人員也有一段時間曾經使用過插件、Java
applet和隱藏框架來模擬這種交互模型。最近發生的變化是,對
XMLHttpRequest
對象的支持已經成為所有平臺上的主流瀏覽器都包括的特性了。JavaScript技術的
XMLHttpRequest
對象是。盡管在正式的JavaScript技術標準中并沒有提到這種對象,然而今天主流的瀏覽器都對他提供了支持。而當代的瀏覽器如Firefox、Internet
Explorer以及Safari在JavaScript技術和CSS的支持上有些細微的差別,但是這種差別是可以處理的。如果你要考慮支持較老的瀏覽器,AJAX也許就不能成為你的解決方法。
基于AJAX的客戶端之所以獨特的原因是客戶端包含了用JavaScript嵌入的特定于頁面的控制邏輯。應用JavaScript技術的頁面基于
事件進行交互,如文檔載入、鼠標點擊、焦點改變甚至是定時器。AJAX交互使得表現層邏輯更加清晰地與數據分離。一個HTML頁面也可以根據需要每次讀入
適當的數據,而不是每次需要顯示一個更改時都重新載入整個頁面。AJAX要求一種不同的服務器架構來支持它這種交互模型。以前,服務器端Web應用關注于
對每個導致服務器調用的客戶端事件都生成HTML文檔。然后客戶端對每個回應都要重新讀入并重新渲染完整的HTML頁面。富Web應用(Rich
Web
Application)關注于,讓一個客戶端獲取一個HTML文檔讓它表現為一個模板或者是一個容器,可以基于事件并使用從服務器端組件中獲取的XML
數據來對文檔注入內容。
一些AJAX交互的應用如:
-
實時表單數據檢驗:
像用戶ID、序列號、郵政編碼或者是特殊的票據代碼這類需要服務器端驗證的數據也可以在用戶提交表單之前進行驗證。
-
自動補全:
像電子郵件地址、姓名或城市名之類的表單數據都可以根據用戶情況自動補全。
-
處理細節操作:
根據一個客戶端事件,一個HTML頁面可以根據現存的一些數據再去獲取更多詳細的信息,如現在有一個產品列表,客戶端可以控制查看單獨的產品信息而無需刷新頁面。
-
復雜的用戶界面控件:
像樹型控件、菜單和進度條之類不要求頁面刷新的控件也能實現。
-
頁面內刷新數據:
HTML頁面可以從服務器上查詢最新的數據如分數、股指、天氣還有其它的特定于應用的數據。
-
服務器端通知:
一個HTML頁面可以通過對服務器進行定時查詢來模擬一個服務器的事件通知推送,實現像通知客戶端一個消息、刷新頁面數據或將客戶端重定向到另一個頁面。
這個列表并未把所有的應用都列出來,但它已經顯示了AJAX交互可以讓Web應用比從前能做更多的事情。但盡管這些好處是值得關注的,這種方式也有
一些缺點:
-
復雜度:
服務器端開發人員必需理解,HTML客戶端頁面中的表現層邏輯以及生成HTML客戶端頁面所需的XML內容的服務器端邏輯。HTML頁面開發人員必須了解
JavaScript技術。如果開發新的框架和發展已有的框架來支持這種交互模型,那么AJAX應用的創建就會越來越簡單。
-
XMLHttpRequest
對象的標準化:
XMLHttpRequest
對象還不是JavaScript技術標準的一部分,這就意味著根據客戶端的不同,應用的行為也有所會不同。 -
JavaScript技術的實現:
AJAX交互極大地依賴于JavaScript技術,而由于客戶端的原因JavaScript還有一些細微的差別。見
QuirksMode.org
來了解更多關于瀏覽器之間區別的內容。
-
調試:
AJAX應用也難于調試,因為流程邏輯是同時嵌在客戶端中和服務器上的。
-
代碼可見:
客戶端的JavaScript可以很容易通過“查看源代碼”被人看見。一個沒有良好設計的AJAX應用很可能被黑客攻擊或被他人剽竊。
當開發人員在使用AJAX交互模型上獲得更多的經驗后,AJAX技術的框架和模式就會慢慢浮現出來。現在就關注于完全通用的AJAX交互框架,還為
時過早。本文和相關的解決方案將關注于在現有的Java 2平臺企業版(J2EE)上如何對AJAX進行支持,像servlet,JavaServer
Page(JSP)軟件、JavaServer Face應用和Java標準標簽庫(JSTL)。
AJAX交互剖析
現在我們已經討論了AJAX是什么以及一些高層次的問題。那現在讓我們把所有的零件放在一起來展示一個具有AJAX的J2EE應用。
首先考慮一個例子。一個Web應用包括了一個靜態HTML頁面,或者是一個由
JSP
生成的HTML頁面,這個JSP中還包括了一個HTML表單,它需要服務器端邏輯來對表單中的數據進行檢驗,而不用刷新頁面。一個名為
ValidateServlet
服務器端組件(
servlet
)用來提供這種驗證邏輯。圖一描述了這種具有驗證邏輯的AJAX交互的細節。
|
圖1: 一個提供驗證邏輯的AJAX交互
|
以下條目代表了圖1中出來AJAX交互的過程:
-
發生一個
客戶端事件
。
-
創建和配置一個
XMLHttpRequest
對象。
-
XMLHttpRequest
對象進行一個調用。 -
ValidateServlet
對請求進行處理。 -
ValidateServlet
返回一個包含了結果的XML文檔。 -
XMLHttpRequest
對象調用
callback()
函數并處理結果。 -
更新 HTML DOM。
現在讓我們逐個研究這個AJAX模型的每一步。
1. 發生一個客戶端事件。
在一個事件發生時可以調用相應的JavaScript函數。在這里,
validate()
函數可以被映射到一個鏈接或者是表單組件的
onkeyup
事件上去。
每次用戶在表單域中按下一個鍵時,表單元素將都調用
validate()
函數。
2. 建立和配置一個
XMLHttpRequest
對象
創建和配置一個
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()
函數建立了一個
XMLHttpRequest
對象并對象中的open函數。open函數需要兩個參數:HTTP方法,可以是
GET
或
POST
; 和對象進行交互的服務器端組件的URL;一個布爾變量,表示是否要進行異步調用。API是
XMLHttpRequest.open(String method, String URL,
boolean asynchronous)
。如果一個交互被設置為異步, (
true
)
那就必須指明一個回調函數。可以使用
req.onreadystatechange = callback;
來設置這個交互的回調函數。詳細內容見第六節。
3.
XMLHttpRequest
對象進行調用
當收到了語句
req.send(null);
,就會進行一次調用。HTTP
GET
的情況下,內容可以是
null
或者留空。當調用
XMLHttpRequest
的這個函數時,也會對已經配置了的URL進行調用。在下面這個例子中,要發送的數據(
id
)將作為一個URL參數。
使用HTTP
GET
,兩個重復的請求將返回同樣的結果。當使用HTTP
GET
方法時,要注意URL的長度,包括已經轉義的URL參數,可能會受到某些瀏覽器和服務器端的Web容器的限制。當發送的數據會影響到服務器端的應用程序的狀態時,就應該使用HTTP
POST
方法。使用HTTP
POST
必須要對
XMLHttpRequest
對象設置一個
Content-Type
頭,使用以下語句:
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); req.send("id=" + escape(idTextField.value));
|
當從JavaScript中發送表單值得時候,你應該考慮對字段值進行編碼。JavaScript中有一個函數
escape()
,應該用他來確保區域化的內容被正確編碼,同時特殊字符也被正確轉義。
4.
ValidateServlet
對請求進行處理.
一個映射到URI "validate" 的servlet將檢驗user ID是不是已經在數據庫中存在了。
一個servlet處理一個
XMLHttpRequest
,就像對待其它的HTTP請求一樣。
下面的例子顯示了服務器從請求中抽取出
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("valid"); } else { response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); response.getWriter().write("invalid"); } } }
|
在這個例子中,一個簡單的HashMap用來存放存在的用戶名。在這個例子中,我們假設用戶的ID是
duke
。
5.
ValidateServlet
返回一個包含結果的XML文檔
用戶ID "duke" 在
users
HashMap
的用戶ID列表中出現了。將在應答中寫一個包含值為
invalid
的
message
元素的XML文檔。更復雜的用例將要求DOM、XSLT或其他API來生成這個應答。
response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); response.getWriter().write("invalid");
|
開發人員必須注意兩個事情。第一,
Content-Type
必須設為
text/xml
。第二,
Cache-Control
必須設為
no-cache
。
XMLHttpRequest
o對象只會處理
Content-Type
為
text/xml
的應答,同時把將
Cache-Control
設為
no-cache
將確保瀏覽器不會從緩存相同的URL(包括參數)返回的應答。
6.
XMLHttpRequest
對象調用
callback()
函數并處理結果。
The
XMLHttpRequest
對象已經配置為當有
readyState
改變的時候就調用
callback()
函數
。讓我們假設已經
ValidateServlet
調用了而且
ValidateServlet
是
4
,表示
XMLHttpRequest
的調用已經完成。HTTP狀態代碼
200
表示一個成功的HTTP交互。
function callback() { if (req.readyState == 4) { if (req.status == 200) { // update the HTML DOM based on whether or not message is valid } } }
|
瀏覽器維護了一個所顯示的文檔的對象形式(也就是所謂的Docuemt Object
Model或DOM)。HTML頁面中的JavaScript可以訪問DOM,同時在頁面載入完之后,可以使用API來修改DOM。
根據成功的請求,JavaScript代碼可以修改HTML頁面的DOM。從
ValidateServlet
獲得的對象形式的XML文檔可以通過
req.responseXML
在JavaScript中獲得,
req
是一個
XMLHttpRequest
對象。DOM API給JavaScript提供了獲取這個文檔中的內容以及修改HTML頁面的DOM的方法。所返回的字符串形式的XML文檔可以通過
req.responseText
獲得。現在我們看看如何在JavaScript中使用DOM API,先看以下從
ValidateServlet
返回的XML文檔。
這個例子是一個簡單的只包含了一個
message
元素的XML片斷,里面只有一個簡單的字符串
valid
或
invalid
。一個更高級的例子可以包含多于一個的消息和可以給用戶看的有效的名字:
function parseMessage() { var message = req.responseXML.getElementsByTagName("message")[0]; setMessage(message.childNodes[0].nodeValue); }
|
parseMessages()
函數將處理一個從
ValidateServlet
獲取的XML文檔。這個函數會調用
setMessage()
with the,并給出
message
作為參數來更新HTML DOM。
7. 更新了HTML DOM
JavaScript技術可以使用很多API從HTML DOM中獲得任何元素對象的引用。推薦的獲得元素引用的方法是調用
document.getElementById("userIdMessage")
,
"userIdMessage"
是HTML文檔中出現的一個元素的ID屬性。有了這個元素的引用,就可以使用JavaScript來修改元素的屬性、修改元素的樣式、添加、刪除或修改子元素。
一個常見的改變元素主體內容的方法是設置元素的
innerHTML
屬性,如下所示:
<script> function setMessage(message) { var userMessageElement = document.getElementById("userIdMessage"); userMessageElement.innerHTML = "" + message + " "; } </script>
|
受到影響的那部分HTML頁面會立刻根據
innerHTML
的設置重新渲染。如果
innerHTML
屬性包含類似
或者是
之類的元素,那么由那些元素所指定的內容同樣會被獲取并渲染。
這種途徑的主要缺點是HTML元素是作為字符串硬編碼在JavaScript中的。JavaScript中硬編碼的HTML標記不是一種好的實踐,因為它
使代碼難于閱讀、維護和修改。我們應該考慮在JavaScript中使用DOM
API來創建和修改HTML元素。把顯示和JavaScript代碼的字符串混在一起只會讓頁面更難于閱讀和編輯。
另一種修改HTML DOM的方法是動態地產生新的元素并把他們作為子元素追加到目標元素,如下面的例子所示:
<script> 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 { // 建立一個新的元素 var fontElement = document.createTextNode("font"); fontElement.setAtribute("id", "userIdMessageFont"); fontElement.setAtribute("color", "red"); userMessageElement.appendChild(fontElement); fontElement.appendChild(messageElement); } } </script>
|
這個范例展示了JavaScript技術的DOM API可以用來更有目的地建立或改變一個元素。當然JavaScript的DOM AP在不同的瀏覽器上也可能有差別,所以你必須在開發應用程序時小心。
Java BluePrint的解決方案目錄
The
Java Blueprints Solutions Catalog
是用來收集J2EE技術上AJAX的最佳實踐的。每個解決方案包含一個問題和方法的描述、一個設計文檔和可運行的源碼。這些解決方案是為了讓你根據需要在自己的應用程序中復用。以下是已經提供的AJAX交互:
自動補全
自動補全
提供了當用戶在一個HTML表單中輸入一個請求時對數據瀏覽的簡化方式。當用戶面對一大片數據時,可以在輸入數據時把可能的完整形式顯示給用戶。然后選擇
其中一個完整形式可以保證用戶輸入的數據已經存在在服務器上。
考慮一個大公司的一個名字查找的Web應用。如圖2所示,只要輸入姓或名的開頭幾個字母就可以得到人的列表。用戶可以然后就只要點擊一下就可以瀏覽用戶的詳細信息。
|
圖2:名字自動補全
|
進度條
在Web
應用中,一個服務器端任務也可能要花一段時間去完成。這段時間很可能會超過HTTP交互的時間上限(超時)。當用戶不知道這個任務什么時候才能完成時,用
戶很可能會重新提交一次表單或直接退出會話狀態。一般來說,Web應用使用頁面刷新來跟蹤服務器端操作的狀態,這種方式可能會讓人厭煩而且也不準確。
AJAX可以用來僅在一個HTML頁面中跟蹤服務器端操作的狀態而無需刷新頁面。用戶可以以圖形方式看到服務器端操作的進度,如圖3。
|
圖3:
進度條
|
刷新數據
向
一個HTML頁面提供最新的數據或服務器消息提醒在現在的Web世界中也是十分重要的,因為現在的Web世界中數據一直不停變化。盡管它不是一個實實在在
的推送技術,但它可以通過使用AJAX交互不斷進行查詢來模擬。當數據需要更新或者要進行提醒,HTML頁面將會動態地改變。圖4顯示了HTML頁面中的
一個服務器端計數器。這個計數器會在頁面后臺自動更新。
|
圖4:服務器端計數器在刷新數據
|
實時檢驗
不是所有的表單域都可以單獨用JavaScript技術在客戶端完成。某些表單數據要求服務器端的驗證邏輯。傳統和Web應用曾使用頁面刷新來完成
這種驗證,但這可能有些讓人煩。
考慮一個需要一個唯一用戶ID的Web應用。使用AJAX交互,用戶可以在輸入的時候就知道ID是否有效(圖5)。
|
圖5:指出用戶ID無效
|
當一個用戶輸入了一個無效的用戶ID,應用程序禁止了提交按鈕并且向用戶顯示了一個信息(圖6)。
|
圖6:用戶ID通過驗證
|
用戶馬上就能知道用戶ID是可用的也是有效的。
最后的思考
我們已經看到AJAX交互可以解決很多問題。配合HTTP處理、數據庫、Web服務、XML處理和業務對象等API,J2EE技術已經提供了一個開
發和部屬基于AJAX應用的一個良好的基礎。有了對于這個交互模型的更好的理解,今天的應用程序可以變得更加有交互性,給最終用戶更好的體驗。
使用AJAX要求你使用支持
XMLHttpRequest
對象的最新瀏覽器版本。使用AJAX還要求大量對JavaScript技術和CSS的應用。作為一個應用程序架構師或是一個開發人員,你要會針對瀏
覽器支持、架構復雜度和對開發人員的培訓等方面來衡量開發一個富應用的需要。當AJAX編程模型不斷地發展,現有的技術和框架會讓這種轉變更加容易。
很明顯的是,突出的Web應用都越來越有交互性了。那么你的呢?
更多信息
Greg Murray 是 Sun Microsystems 的一名工程師,是servlet標準的領導人,BluePrint小組的前成員,在這個小組時他已經開始關注Web層次問題。他也是《
Enterprise Applications With the Java 2 Platform,Enterprise Edition
and
Designing Web Services With the J2EE 1.4 Platform
(Addison-Wesley)》一書的協助編撰者。
http://www2.uuzone.com/app/trackBack.do?type=blog&trackBackID=39739