理解
Ajax 編程的基本知識(shí)
是重要的,但是如果正在構(gòu)建復(fù)雜的用戶界面,那么能夠在更高層次的抽象上工作也很重要。在面向 Java 開發(fā)人員的 Ajax 系列的第 3 篇文章中,我在上個(gè)月的
Ajax 的數(shù)據(jù)序列化技術(shù)
基礎(chǔ)之上,介紹一種可以避免繁瑣的 Java 對(duì)象序列化細(xì)節(jié)的技術(shù)。
在
上一篇文章
中,我介紹了如何用 JavaScript 對(duì)象標(biāo)注(JSON)以一種在客戶機(jī)上容易轉(zhuǎn)化成 JavaScript 對(duì)象的格式對(duì)數(shù)據(jù)進(jìn)行序列化。有了這個(gè)設(shè)置,就可以用 JavaScript 代碼調(diào)用遠(yuǎn)程服務(wù),并在響應(yīng)中接收 JavaScript 對(duì)象圖,但是又不像遠(yuǎn)程過程調(diào)用。這一次,將學(xué)習(xí)如何更進(jìn)一步,使用一個(gè)框架,把從 JavaScript 客戶代碼對(duì)服務(wù)器端 Java 對(duì)象進(jìn)行遠(yuǎn)程調(diào)用的能力正式化。
DWR 是一個(gè)開放源碼的使用 Apache 許可協(xié)議的解決方案,它包含服務(wù)器端 Java 庫(kù)、一個(gè) DWR servlet 以及 JavaScript 庫(kù)。雖然 DWR 不是 Java 平臺(tái)上唯一可用的 Ajax-RPC 工具包,但是它是最成熟的,而且提供了許多有用的功能。請(qǐng)參閱
參考資料
,在繼續(xù)學(xué)習(xí)之前下載 DWR。
DWR 是什么?
從最簡(jiǎn)單的角度來(lái)說,DWR 是一個(gè)引擎,可以把服務(wù)器端 Java 對(duì)象的方法公開給 JavaScript 代碼。使用 DWR 可以有效地從應(yīng)用程序代碼中把 Ajax 的全部請(qǐng)求-響應(yīng)循環(huán)消除掉。這意味著客戶端代碼再也不需要直接處理 XMLHttpRequest
對(duì)象或者服務(wù)器的響應(yīng)。不再需要編寫對(duì)象的序列化代碼或者使用第三方工具才能把對(duì)象變成 XML。甚至不再需要編寫 servlet 代碼把 Ajax 請(qǐng)求調(diào)整成對(duì) Java 域?qū)ο蟮恼{(diào)用。
DWR 是作為 Web 應(yīng)用程序中的 servlet 部署的。把它看作一個(gè)黑盒子,這個(gè) servlet 有兩個(gè)主要作用:首先,對(duì)于公開的每個(gè)類,DWR 動(dòng)態(tài)地生成包含在 Web 頁(yè)面中的 JavaScript。生成的 JavaScript 包含存根函數(shù),代表 Java 類上的對(duì)應(yīng)方法并在幕后執(zhí)行 XMLHttpRequest
。這些請(qǐng)求被發(fā)送給 DWR,這時(shí)它的第二個(gè)作用就是把請(qǐng)求翻譯成服務(wù)器端 Java 對(duì)象上的方法調(diào)用并把方法的返回值放在 servlet 響應(yīng)中發(fā)送回客戶端,編碼成 JavaScript。DWR 還提供了幫助執(zhí)行常見的用戶界面任務(wù)的 JavaScript 工具函數(shù)。
關(guān)于示例
在更詳細(xì)地解釋 DWR 之前,我要介紹一個(gè)簡(jiǎn)單的示例場(chǎng)景。像在前一篇文章中一樣,我將采用一個(gè)基于在線商店的最小模型,這次包含一個(gè)基本的產(chǎn)品表示、一個(gè)可以包含產(chǎn)品商品的用戶購(gòu)物車以及一個(gè)從數(shù)據(jù)存儲(chǔ)查詢產(chǎn)品的數(shù)據(jù)訪問對(duì)象(DAO)。Item
類與前一篇文章中使用的一樣,但是不再實(shí)現(xiàn)任何手工序列化方法。圖 1 說明了這個(gè)簡(jiǎn)單的設(shè)置:
圖 1. 說明 Cart、CatalogDAO 和 Item 類的類圖
在這個(gè)場(chǎng)景中,我將演示兩個(gè)非常簡(jiǎn)單的用例。第一,用戶可以在目錄中執(zhí)行文本搜索并查看匹配的商品。第二,用戶可以添加商品到購(gòu)物車中并查看購(gòu)物車中商品的總價(jià)。
實(shí)現(xiàn)目錄
DWR 應(yīng)用程序的起點(diǎn)是編寫服務(wù)器端對(duì)象模型。在這個(gè)示例中,我從編寫 DAO 開始,用它提供對(duì)產(chǎn)品目錄數(shù)據(jù)存儲(chǔ)的搜索功能。CatalogDAO.java
是一個(gè)簡(jiǎn)單的無(wú)狀態(tài)的類,有一個(gè)無(wú)參數(shù)的構(gòu)造函數(shù)。清單 1 顯示了我想要公開給 Ajax 客戶的 Java 方法的簽名:
清單 1. 通過 DWR 公開的 CatalogDAO 方法
/**
* Returns a list of items in the catalog that have
* names or descriptions matching the search expression
* @param expression Text to search for in item names
* and descriptions
* @return list of all matching items
*/
public List<Item> findItems(String expression);
/**
* Returns the Item corresponding to a given Item ID
* @param id The ID code of the item
* @return the matching Item
*/
public Item getItem(String id);
|
接下來(lái),我需要配置 DWR,告訴它 Ajax 客戶應(yīng)當(dāng)能夠構(gòu)建 CatalogDAO
并調(diào)用這些方法。我在清單 2 所示的 dwr.xml 配置文件中做這些事:
清單 2. 公開 CatalogDAO 方法的配置
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
"http://www.getahead.ltd.uk/dwr/dwr10.dtd">
<dwr>
<allow>
<create creator="new" javascript="catalog">
<param name="class"
value="developerworks.ajax.store.CatalogDAO"/>
<include method="getItem"/>
<include method="findItems"/>
</create>
<convert converter="bean"
match="developerworks.ajax.store.Item">
<param name="include"
value="id,name,description,formattedPrice"/>
</convert>
</allow>
</dwr>
|
dwr.xml 文檔的根元素是 dwr
。在這個(gè)元素內(nèi)是 allow
元素,它指定 DWR 進(jìn)行遠(yuǎn)程的類。allow
的兩個(gè)子元素是 create
和 convert
。
create 元素
create
元素告訴 DWR 應(yīng)當(dāng)公開給 Ajax 請(qǐng)求的服務(wù)器端類,并定義 DWR 應(yīng)當(dāng)如何獲得要進(jìn)行遠(yuǎn)程的類的實(shí)例。這里的 creator
屬性被設(shè)置為值 new
,這意味著 DWR 應(yīng)當(dāng)調(diào)用類的默認(rèn)構(gòu)造函數(shù)來(lái)獲得實(shí)例。其他的可能有:通過代碼段用 Bean 腳本框架(Bean Scripting Framework,BSF)創(chuàng)建實(shí)例,或者通過與 IOC 容器 Spring 進(jìn)行集成來(lái)獲得實(shí)例。默認(rèn)情況下,到 DWR 的 Ajax 請(qǐng)求會(huì)調(diào)用 creator
,實(shí)例化的對(duì)象處于頁(yè)面范圍內(nèi),因此請(qǐng)求完成之后就不再可用。在無(wú)狀態(tài)的 CatalogDAO
情況下,這樣很好。
create
的 javascript
屬性指定從 JavaScript 代碼訪問對(duì)象時(shí)使用的名稱。嵌套在 create
元素內(nèi)的 param
元素指定 creator
要?jiǎng)?chuàng)建的 Java 類。最后,include
元素指定應(yīng)當(dāng)公開的方法的名稱。顯式地說明要公開的方法是避免偶然間允許訪問有害功能的良好實(shí)踐 —— 如果漏了這個(gè)元素,類的所有方法都會(huì)公開給遠(yuǎn)程調(diào)用。反過來(lái),可以用 exclude
元素指定那些想防止被訪問的方法。
convert 元素
creator
負(fù)責(zé)公開用于 Web 遠(yuǎn)程的類和類的方法,convertor
則負(fù)責(zé)這些方法的參數(shù)和返回類型。convert
元素的作用是告訴 DWR 在服務(wù)器端 Java 對(duì)象表示和序列化的 JavaScript 之間如何轉(zhuǎn)換數(shù)據(jù)類型。
DWR 自動(dòng)地在 Java 和 JavaScript 表示之間調(diào)整簡(jiǎn)單數(shù)據(jù)類型。這些類型包括 Java 原生類型和它們各自的類表示,還有 String、Date、數(shù)組和集合類型。DWR 也能把 JavaBean 轉(zhuǎn)換成 JavaScript 表示,但是出于安全性的原因,做這件事要求顯式的配置。
清單 2
中的 convert
元素告訴 DWR 用自己基于反射的 bean 轉(zhuǎn)換器處理 CatalogDAO
的公開方法返回的 Item
,并指定序列化中應(yīng)當(dāng)包含 Item
的哪個(gè)成員。成員的指定采用 JavaBean 命名規(guī)范,所以 DWR 會(huì)調(diào)用對(duì)應(yīng)的 get
方法。在這個(gè)示例中,我去掉了數(shù)字的 price
字段,而是包含了 formattedPrice
字段,它采用貨幣格式進(jìn)行顯示。
現(xiàn)在,我準(zhǔn)備把 dwr.xml 部署到 Web 應(yīng)用程序的 WEB-INF
目錄,在那里 DWR servlet 會(huì)讀取它。但是,在繼續(xù)之前,確保每件事都按照希望的那樣運(yùn)行是個(gè)好主意。
測(cè)試部署
如果 DWRServlet
的 web.xml
定義把 init-param
debug
設(shè)置為 true
,那么就啟用了 DWR 非常有幫助的測(cè)試模式。導(dǎo)航到 /{your-web-app}/dwr/
會(huì)把 DWR 配置的要進(jìn)行遠(yuǎn)程的類列表顯示出來(lái)。在其中點(diǎn)擊,會(huì)進(jìn)入指定類的狀態(tài)屏幕。CatalogDAO
的 DWR 測(cè)試頁(yè)如圖 2 所示。除了提供粘貼到 Web 頁(yè)面的 script
標(biāo)記(指向 DWR 為類生成的 JavaScript)之外,這個(gè)屏幕還提供了類的方法列表。這個(gè)列表包括從類的超類繼承的方法,但是只有在 dwr.xml
中顯式地指定為遠(yuǎn)程的才標(biāo)記為可訪問。
圖 2. CatalogDAO 的 DWR 測(cè)試頁(yè)
可以在可訪問的方法旁邊的文本框中輸入?yún)?shù)值并點(diǎn)擊 Execute 按鈕調(diào)用方法。服務(wù)器的響應(yīng)將在警告框中用 JSON 標(biāo)注顯示出來(lái),如果是簡(jiǎn)單值,就會(huì)內(nèi)聯(lián)在方法旁邊直接顯示。這個(gè)測(cè)試頁(yè)非常有用。它們不僅允許檢查公開了哪個(gè)類和方法用于遠(yuǎn)程,還可以測(cè)試每個(gè)方法是否像預(yù)期的那樣工作。
如果對(duì)遠(yuǎn)程方法的工作感到滿意,就可以用 DWR 生成的 JavaScript 存根從客戶端代碼調(diào)用服務(wù)器端對(duì)象。
調(diào)用遠(yuǎn)程對(duì)象
遠(yuǎn)程 Java 對(duì)象方法和對(duì)應(yīng)的 JavaScript 存根函數(shù)之間的映射很簡(jiǎn)單。通用的形式是 JavaScriptName.methodName(methodParams ..., callBack)
,其中 JavaScriptName
是 creator
的 javascript
屬性指定的名稱,methodParams
代表 Java 方法的 n 個(gè)參數(shù),callback
是要用 Java 方法的返回值調(diào)用的 JavaScript 函數(shù)。如果熟悉 Ajax,可以看出這個(gè)回調(diào)機(jī)制是 XMLHttpRequest
異步性的常用方式。
在示例場(chǎng)景中,我用清單 3 中的 JavaScript 函數(shù)執(zhí)行搜索,并用搜索結(jié)果更新用戶界面。這個(gè)清單還使用來(lái)自 DWR 的 util.js
的便捷函數(shù)。要特別說明的是名為 $()
的 JavaScript 函數(shù),可以把它當(dāng)作 document.getElementById()
的加速版。錄入它當(dāng)然更容易。如果您使用過 JavaScript 原型庫(kù),應(yīng)當(dāng)熟悉這個(gè)函數(shù)。
清單 3. 從客戶機(jī)調(diào)用遠(yuǎn)程的 findItems()
/*
* Handles submission of the search form
*/
function searchFormSubmitHandler() {
// Obtain the search expression from the search field
var searchexp = $("searchbox").value;
// Call remoted DAO method, and specify callback function
catalog.findItems(searchexp, displayItems);
// Return false to suppress form submission
return false;
}
/*
* Displays a list of catalog items
*/
function displayItems(items) {
// Remove the currently displayed search results
DWRUtil.removeAllRows("items");
if (items.length == 0) {
alert("No matching products found");
$("catalog").style.visibility = "hidden";
} else {
DWRUtil.addRows("items",items,cellFunctions);
$("catalog").style.visibility = "visible";
}
}
|
在上面的 searchFormSubmitHandler()
函數(shù)中,我們感興趣的代碼當(dāng)然是 catalog.findItems(searchexp, displayItems);
。這一行代碼就是通過網(wǎng)絡(luò)向 DWR servlet 發(fā)送 XMLHttpRequest
并用遠(yuǎn)程對(duì)象的響應(yīng)調(diào)用 displayItems()
函數(shù)所需要的全部?jī)?nèi)容。
displayItems()
回調(diào)本身是由一個(gè) Item
數(shù)組表示調(diào)用的。這個(gè)數(shù)組傳遞給 DWRUtil.addRows()
便捷函數(shù),同時(shí)還有要填充的表的 ID 和一個(gè)函數(shù)數(shù)組。表中每行有多少單元格,這個(gè)數(shù)組中就有多少個(gè)函數(shù)。按照順序使用來(lái)自數(shù)組的 Item
逐個(gè)調(diào)用每個(gè)函數(shù),并用返回的內(nèi)容填充對(duì)應(yīng)的單元格。
在這個(gè)示例中,我想讓商品表中的每一行都顯示商品的名稱、說明和價(jià)格,并在最后一列顯示商品的 Add to Cart 按鈕。清單 4 顯示了實(shí)現(xiàn)這一功能的單元格函數(shù)數(shù)組:
清單 4. 填充商品表的單元格函數(shù)數(shù)組
/*
* Array of functions to populate a row of the items table
* using DWRUtil′s addRows function
*/
var cellFunctions = [
function(item) { return item.name; },
function(item) { return item.description; },
function(item) { return item.formattedPrice; },
function(item) {
var btn = document.createElement("button");
btn.innerHTML = "Add to cart";
btn.itemId = item.id;
btn.onclick = addToCartButtonHandler;
return btn;
}
];
|
前三個(gè)函數(shù)只是返回 dwr.xml 中 Item
的 convertor
包含的字段內(nèi)容。最后一個(gè)函數(shù)創(chuàng)建一個(gè)按鈕,把 Item
的 ID 賦給它,并指定在點(diǎn)擊按鈕時(shí)應(yīng)當(dāng)調(diào)用名為 addToCartButtonHandler
的函數(shù)。這個(gè)函數(shù)是第二個(gè)用例的入口點(diǎn):向購(gòu)物車中添加 Item
。
實(shí)現(xiàn)購(gòu)物車
|
DWR 的安全性
DWR 設(shè)計(jì)時(shí)就考慮了安全性。使用 dwr.xml 明確地列出那些想做遠(yuǎn)程處理的類和方法,可以避免意外地把那些可能被惡意利用的功能公開出去。除此之外,使用調(diào)試測(cè)試模式,可以容易地審計(jì)所有公開到 Web 上的類和方法。
DWR 也支持基于角色的安全性。通過 bean 的 creator 配置,可以指定用戶訪問特定 bean 所必須屬于的 J2EE 角色。通過部署多個(gè) URL 受保護(hù)的 DWRServlet 實(shí)例,每個(gè)實(shí)例都有自己的 dwr.xml 配置文件,也可以提供擁有不同遠(yuǎn)程功能的用戶集。
|
|
用戶購(gòu)物車的 Java 表示基于 Map
。當(dāng) Item
添加到購(gòu)物車中時(shí),Item
本身作為鍵被插入 Map
。 Map
中對(duì)應(yīng)的值是一個(gè) Integer
,代表購(gòu)物車中指定 Item
的數(shù)量。所以 Cart.java
有一個(gè)字段 contents
,聲明為 Map<Item,Integer>
。
使用復(fù)雜類型作為哈希鍵給 DWR 帶來(lái)一個(gè)問題 —— 在 JavaScript 中,數(shù)組的鍵必須是標(biāo)量的。所以,DWR 無(wú)法轉(zhuǎn)換 contents
Map
。但是,對(duì)于購(gòu)物車用戶界面來(lái)說,用戶需要查看的只是每個(gè)商品的名稱和數(shù)量。所以我向 Cart
添加了一個(gè)名為 getSimpleContents()
的方法,它接受 contents
Map
并根據(jù)它構(gòu)建一個(gè)簡(jiǎn)化的 Map<String,Integer>
,只代表每個(gè) Item
的名稱和數(shù)量。這個(gè)用字符串作為鍵的 map 表示可以由 DWR 的轉(zhuǎn)換器轉(zhuǎn)換成 JavaScript。
客戶對(duì) Cart
感興趣的其他字段是 totalPrice
,它代表購(gòu)物車中所有商品的金額匯總。使用 Item
,我還提供了一個(gè)合成的成員叫作 formattedTotalPrice
,它是金額匯總的格式化好的 String
表示。
轉(zhuǎn)換購(gòu)物車
為了不讓客戶代碼對(duì) Cart
做兩個(gè)調(diào)用(一個(gè)獲得內(nèi)容,一個(gè)獲得總價(jià)),我想把這些數(shù)據(jù)一次全都發(fā)給客戶。為了做到這一點(diǎn),我添加了一個(gè)看起來(lái)有點(diǎn)兒怪的方法,如清單 5 所示:
清單 5. Cart.getCart() 方法
/**
* Returns the cart itself - for DWR
* @return the cart
*/
public Cart getCart() {
return this;
}
|
雖然這個(gè)方法在普通的 Java 代碼中可能完全是多余的(因?yàn)樵谡{(diào)用這個(gè)方法時(shí),已經(jīng)有對(duì) Cart
的引用),但它允許 DWR 客戶讓 Cart
把自己序列化成 JavaScript。
除了 getCart()
,需要遠(yuǎn)程化的另一個(gè)方法是 addItemToCart()
。這個(gè)方法接受目錄 Item 的 ID 的 String
表示,把這個(gè)商品添加到 Cart
中并更新總價(jià)。方法還返回 Cart
,這樣客戶代碼在一個(gè)操作中就能更新 Cart
的內(nèi)容并接收購(gòu)物車的新狀態(tài)。
清單 6 是擴(kuò)展的 dwr.xml 配置文件,包含 Cart
類進(jìn)行遠(yuǎn)程所需要的額外配置:
清單 6. 修改過的 dwr.xml 包含了 Cart 類
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
"http://www.getahead.ltd.uk/dwr/dwr10.dtd">
<dwr>
</allow>
</create creator="new" javascript="catalog">
</param name="class"
value="developerworks.ajax.store.CatalogDAO"/>
</include method="getItem"/>
</include method="findItems"/>
<//create>
</convert converter="bean"
match="developerworks.ajax.store.Item">
</param name="include"
value="id,name,description,formattedPrice"/>
<//convert>
</create creator="new" scope="session" javascript="Cart">
</param name="class"
value="developerworks.ajax.store.Cart"/>
</include method="addItemToCart"/>
</include method="getCart"/>
<//create>
</convert converter="bean"
match="developerworks.ajax.store.Cart">
</param name="include"
value="simpleContents,formattedTotalPrice"/>
<//convert>
<//allow>
</dwr>
|
在這個(gè)版本的 dwr.xml
中,我添加了 Cart
的 creator
和 convertor
。create
元素指定應(yīng)當(dāng)把 addItemToCart()
和 getCart()
方法遠(yuǎn)程化,而且重要的是,生成的 Cart
實(shí)例應(yīng)當(dāng)放在用戶的會(huì)話中。所以,購(gòu)物車的內(nèi)容在用戶的請(qǐng)求之間會(huì)保留。
Cart
的 convert
元素是必需的,因?yàn)檫h(yuǎn)程的 Cart
方法返回的是 Cart
本身。在這里我指定在 Cart
的序列化 JavaScript 形式中應(yīng)當(dāng)存在的成員是 simpleContents
這個(gè)圖和 formattedTotalPrice
這個(gè)字符串。
如果對(duì)這覺得有點(diǎn)兒不明白,那么只要記住 create
元素指定的是 DWR 客戶可以調(diào)用的 Cart
服務(wù)器端方法,而 convert
元素指定在 Cart
的 JavaScript 序列化形式中包含的成員。
現(xiàn)在可以實(shí)現(xiàn)調(diào)用 Cart
的遠(yuǎn)程方法的客戶端代碼了。
調(diào)用遠(yuǎn)程的 Cart 方法
首先,當(dāng)商店的 Web 頁(yè)首次裝入時(shí),我想檢查保存在會(huì)話中的 Cart
的狀態(tài),看是否已經(jīng)有一個(gè)購(gòu)物車了。這是必需的,因?yàn)橛脩艨赡芤呀?jīng)向 Cart
中添加了商品,然后刷新了頁(yè)面或者導(dǎo)航到其他地方之后又返回來(lái)。在這些情況下,重新載入的頁(yè)面需要用會(huì)話中的 Cart
數(shù)據(jù)對(duì)自己進(jìn)行同步。我可以在頁(yè)面的 onload 函數(shù)中用一個(gè)調(diào)用做到這一點(diǎn),就像這樣:Cart.getCart(displayCart)
。請(qǐng)注意 displayCart()
是一個(gè)回調(diào)函數(shù),由服務(wù)器返回的 Cart
響應(yīng)數(shù)據(jù)調(diào)用。
如果 Cart
已經(jīng)在會(huì)話中,那么creator
會(huì)檢索它并調(diào)用它的 getCart()
方法。如果會(huì)話中沒有 Cart
,那么 creator
會(huì)實(shí)例化一個(gè)新的,把它放在會(huì)話中,并調(diào)用 getCart()
方法。
清單 7 顯示了 addToCartButtonHandler()
函數(shù)的實(shí)現(xiàn),當(dāng)點(diǎn)擊商品的 Add to Cart 按鈕時(shí)會(huì)調(diào)用這個(gè)函數(shù):
清單 7. addToCartButtonHandler() 實(shí)現(xiàn)
/*
* Handles a click on an Item′s "Add to Cart" button
*/
function addToCartButtonHandler() {
// ′this′ is the button that was clicked.
// Obtain the item ID that was set on it, and
// add to the cart.
Cart.addItemToCart(this.itemId,displayCart);
}
|
由 DWR 負(fù)責(zé)所有通信,所以客戶上的添加到購(gòu)物車行為就是一個(gè)函數(shù)。清單 8 顯示了這個(gè)示例的最后一部分 —— displayCart()
回調(diào)的實(shí)現(xiàn),它用 Cart
的狀態(tài)更新用戶界面:
清單 8. displayCart() 實(shí)現(xiàn)
/*
* Displays the contents of the user′s shopping cart
*/
function displayCart(cart) {
// Clear existing content of cart UI
var contentsUL = $("contents");
contentsUL.innerHTML="";
// Loop over cart items
for (var item in cart.simpleContents) {
// Add a list element with the name and quantity of item
var li = document.createElement("li");
li.appendChild(document.createTextNode(
cart.simpleContents[item] + " x " + item
));
contentsUL.appendChild(li);
}
// Update cart total
var totalSpan = $("totalprice");
totalSpan.innerHTML = cart.formattedTotalPrice;
}
|
在這里重要的是要記住,simpleContents
是一個(gè)把 String
映射到數(shù)字的 JavaScript 數(shù)組。每個(gè)字符串都是一個(gè)商品的名稱,關(guān)聯(lián)數(shù)組中的對(duì)應(yīng)數(shù)字就是購(gòu)物車中該商品的數(shù)量。所以表達(dá)式 cart.simpleContents[item] + " x " + item
可能就會(huì)計(jì)算出 “2 x Oolong 128MB CF Card
” 這樣的結(jié)果。
DWR 商店應(yīng)用程序
圖 3 顯示了這個(gè)基于 DWR 的 Ajax 應(yīng)用程序的使用情況:顯示了通過搜索檢索到的商品,并在右側(cè)顯示用戶的購(gòu)物車:
圖 3. 基于 DWR 的 Ajax 商店應(yīng)用程序的使用情況
DWR 的利弊
|
調(diào)用批處理
在 DWR 中,可以在一個(gè) HTTP 請(qǐng)求中向服務(wù)器發(fā)送多個(gè)遠(yuǎn)程調(diào)用。調(diào)用 DWREngine.beginBatch() 告訴 DWR 不要直接分派后續(xù)的遠(yuǎn)程調(diào)用,而是把它們組合到一個(gè)批請(qǐng)求中。DWREngine.endBatch() 調(diào)用則把批請(qǐng)求發(fā)送到服務(wù)器。遠(yuǎn)程調(diào)用在服務(wù)器端順序執(zhí)行,然后調(diào)用每個(gè) JavaScript 回調(diào)。
批處理在兩方面有助于降低延遲:第一,避免了為每個(gè)調(diào)用創(chuàng)建 XMLHttpRequest 對(duì)象并建立相關(guān)的 HTTP 連接的開銷。第二,在生產(chǎn)環(huán)境中,Web 服務(wù)器不必處理過多的并發(fā) HTTP 請(qǐng)求,改進(jìn)了響應(yīng)時(shí)間。
|
|
現(xiàn)在可以看出用 DWR 實(shí)現(xiàn)由 Java 支持的 Ajax 應(yīng)用程序有多么容易了。雖然示例場(chǎng)景很簡(jiǎn)單,我實(shí)現(xiàn)用例的手段也盡可能少,但是不應(yīng)因此而低估 DWR 引擎相對(duì)于自己設(shè)計(jì) Ajax 應(yīng)用程序可以節(jié)約的工作量。在前一篇文章中,我介紹了手工設(shè)計(jì) Ajax 請(qǐng)求和響應(yīng)、把 Java 對(duì)象圖轉(zhuǎn)化成 JSON 表示的全部步驟,在這篇文章中,DWR 替我做了所有這些工作。我只編寫了不到 50 行 JavaScript 就實(shí)現(xiàn)了客戶機(jī),而在服務(wù)器端,我需要做的所有工作就是給常規(guī)的 JavaBean 加上一些額外方法。
當(dāng)然,每種技術(shù)都有它的不足。同任何 RPC 機(jī)制一樣,在 DWR 中,可能很容易忘記對(duì)于遠(yuǎn)程對(duì)象進(jìn)行的每個(gè)調(diào)用都要比本地函數(shù)調(diào)用昂貴得多。DWR 在隱藏 Ajax 的機(jī)械性方面做得很好,但是重要的是要記住網(wǎng)絡(luò)并不是透明的 —— 進(jìn)行 DWR 調(diào)用會(huì)有延遲,所以應(yīng)用程序的架構(gòu)應(yīng)當(dāng)讓遠(yuǎn)程方法的粒度比較粗。正是為了這個(gè)目的,addItemToCart()
才返回 Cart
本身。雖然讓 addItemToCart()
作為一個(gè) void 方法可能更自然,但是這樣的話對(duì)它的每個(gè) DWR 調(diào)用后面都必須跟著一個(gè) getCart()
調(diào)用以檢索修改后的 Cart
狀態(tài)。
對(duì)于延遲,DWR 在調(diào)用的批處理中有自己的解決方案(請(qǐng)參閱側(cè)欄的
調(diào)用批處理
)。如果不能為應(yīng)用程序提供適當(dāng)粗粒度的 Ajax 接口,那么只要有可能把多個(gè)遠(yuǎn)程調(diào)用組合到一個(gè) HTTP 請(qǐng)求中,就請(qǐng)使用調(diào)用批處理。
分離的問題
從實(shí)質(zhì)上看,DWR 在客戶端和服務(wù)器端代碼間形成了緊密的耦合,這有許多含義:首先,遠(yuǎn)程方法 API 的變化需要在 DWR 存根調(diào)用的 JavaScript 上反映出來(lái)。第二(也是最明顯的),這種耦合會(huì)造成對(duì)客戶端的考慮會(huì)滲入服務(wù)器端代碼。例如,因?yàn)椴皇撬?Java 類型都能轉(zhuǎn)化成 JavaScript,所以有時(shí)有必要給 Java 對(duì)象添加額外方法,好讓它能夠更容易地遠(yuǎn)程化。在示例場(chǎng)景中,我通過把 getSimpleContents()
方法添加到 Cart
來(lái)解決這個(gè)問題。我還添加了 getCart()
方法,它在 DWR 場(chǎng)景中是有用的,但在其他場(chǎng)景中則完全是多余的。由于遠(yuǎn)程對(duì)象粗粒度 API 的需要以及把某些 Java 類型轉(zhuǎn)化成 JavaScript 的問題,所以可以看到遠(yuǎn)程 JavaBean 會(huì)被那些只對(duì) Ajax 客戶有用的方法“污染”。
為了克服這個(gè)問題,可以使用包裝器類把額外的特定于 DWR 的方法添加到普通 JavaBean。這意味著 JavaBean 類的 Java 客戶可能看不到與遠(yuǎn)程相關(guān)聯(lián)的額外的毛病,而且也允許給遠(yuǎn)程方法提供更友好的名稱 —— 例如用 getPrice()
代替 getFormattedPrice()
。圖 4 顯示的 RemoteCart
類對(duì) Cart
進(jìn)行了包裝,添加了額外的 DWR 功能:
圖 4. RemoteCart 為遠(yuǎn)程功能對(duì) Cart 做了包裝
最后,需要記住:DWR Ajax 調(diào)用是異步的,所以不要期望它們會(huì)按照分派的順序返回。在示例代碼中我忽略了這個(gè)小問題,但是在這個(gè)系列的第一篇文章中,我演示了如何為響應(yīng)加時(shí)間戳,以此作為保證數(shù)據(jù)到達(dá)順序的一種簡(jiǎn)單手段。
結(jié)束語(yǔ)
正如所看到的,DWR 提供了許多東西 —— 它允許迅速而簡(jiǎn)單地創(chuàng)建到服務(wù)器端域?qū)ο蟮?Ajax 接口,而不需要編寫任何 servlet 代碼、對(duì)象序列化代碼或客戶端 XMLHttpRequest
代碼。使用 DWR 部署到 Web 應(yīng)用程序極為簡(jiǎn)單,而且 DWR 的安全性特性可以與 J2EE 基于角色的驗(yàn)證系統(tǒng)集成。但是 DWR 并不是對(duì)于任何一種應(yīng)用程序架構(gòu)都適合,所以在設(shè)計(jì)域?qū)ο蟮?API 時(shí)需要做些考慮。
如果想學(xué)習(xí)用 DWR 進(jìn)行 Ajax 的利弊的更多內(nèi)容,最好的方式就是下載并開始實(shí)踐。DWR 有許多我沒有介紹的特性,
文章源代碼
是把 DWR 投入使用的一個(gè)良好起點(diǎn)。請(qǐng)參閱
參考資料
,學(xué)習(xí)關(guān)于 Ajax、DWR 和相關(guān)技術(shù)的更多內(nèi)容。
這個(gè)系列中要指出的最重要的一點(diǎn)是:對(duì)于 Ajax 應(yīng)用程序,沒有包治百病的解決方案。Ajax 是一個(gè)快速發(fā)展的領(lǐng)域,不斷有新技術(shù)涌現(xiàn)。在這個(gè)系列的三篇文章中,我的重點(diǎn)在于帶您開始在 Ajax 應(yīng)用程序的 Web 層中利用 Java 技術(shù) —— 不管是選擇基于 XMLHttpRequest
的帶有對(duì)象序列化框架的技術(shù),還是選擇 DWR 這樣的更高級(jí)抽象。請(qǐng)?jiān)诤罄m(xù)幾個(gè)月中留意面向 Java 開發(fā)人員介紹 Ajax 的文章。
下載
描述
|
名字
|
大小
|
?下載方法
|
DWR source code
|
j-ajax3dwr.zip
|
301 KB
|
?
FTP
|
參考資料
學(xué)習(xí)
凡是有該標(biāo)志的文章,都是該blog博主Caoer(草兒)原創(chuàng),凡是索引、收藏
、轉(zhuǎn)載請(qǐng)注明來(lái)處和原文作者。非常感謝。