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