程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 面向Java開發人員的Ajax:Java對象序列化

面向Java開發人員的Ajax:Java對象序列化

編輯:關於JAVA

本文我們討論 AJax 開發的基礎知識,但是將側重於許多 Java Web 開發人員最關心的問題:為客戶機生成數據。

多數 Java 開發人員已經把模型-視圖-控制器(MVC)模式應用在他們的 Web 應用程序上。在傳統的 Web 應用程序中,視圖組件由 JSP 或者其他表示技術(例如 Velocity 模板)構成。

這些表示組件動態地生成全新的 Html 頁面,替代用戶以前正在查看的頁面,從而更新用戶界面。但是,在 Java Web 應用程序使用 AJax UI 的情況下,基於從 XMLHttpRequest 的響應接收到的數據,JavaScript 客戶端代碼對於更新用戶看到的內容負有最終責任。從服務器的角度來看,視圖成為它響應客戶機請求而發送的數據表示。

本文側重於可以用來生成 Java 對象以數據為中心的視圖的技術。我將演示可以把 JavaBeans 變成 XML 文檔的各種方法,並且討論每種方法的優劣。您將看到為什麼 XML 並不總是最好的途徑:對於簡單的 AJax 請求來說,傳輸純文本更好。

最後,我還將介紹 JavaScript 對象標注(JSON)。JSON 允許數據以序列化的 Javascript 對象圖的形式傳輸,在客戶端代碼中處理序列化的 JavaScript 對象圖極為容易。

關於示例

我將使用一個示例應用程序和幾個用例來演示這裡討論的技術特性和技術。圖 1 顯示的極為簡單的數據模型可以表示示例用例。這個模型代表在線商店中的顧客帳戶。顧客擁有以前訂單的集合,每個訂單包含幾個商品。

雖然 XMLHttpRequest 對於發送數據使用的格式沒有做任何限制,但是對於多數目的來說,只發送傳統的表單數據是適合的,所以我的討論集中在服務器的響應上。

響應也可以有基於文本的格式,但是正如它的名字表示的,XMLHttpRequest 具有內置的處理 XML 響應數據的能力。這使 XML 成為 AJax 響應的默認選擇,所以我們從 XML 格式開始討論。

從 Java 類產生 XML

把 Ajax 響應作為 XML 來傳遞有許多原因:每個支持 AJax 的浏覽器都有導航 XML 文檔的方法,也有許多服務器端技術可以處理 XML 數據。

通過制定一個方案,描述要交換的文檔類型,在 AJax 客戶端和服務器端之間很容易定義合約,而且如果服務器端架構采用面向服務的方式,那麼使用 XML 也可以允許非 AJax 客戶機使用您提供的數據。

我將考慮從 Java 對象產生 XML 數據的三種方法,並討論每種方法的優劣。 

自行進行序列化

首先,可以從對象圖以編程的方式生成 XML。這種方式可以簡單到只是在每個 JavaBean 類中實現 toXml() 方法即可。然後就可以選擇合適的 XML API,讓每個 bean 提供表示自己狀態的元素,並遞歸地對自己的成員調用對象圖。

顯然,這種方式無法擴展到大量的類,因為每個類都需要專門編寫自己的 XML 生成代碼。從好的方面來看,這是一個實現起來簡單的方式,沒有額外的配置支出或者更復雜的構建過程支出,任何 JavaBean 圖都可以只用幾個調用就變成 XML 文檔。

我曾把XML標記字符串連接在一起,實現了toXml()方法。上次我就提到過,這是個糟糕的方法,因為它把確保標記配對、實體編碼等工作的負擔放在每個 toXML() 方法的代碼中。

在 Java 平台上有幾個 XML API 可以替您做這些工作,這樣您就可以把精力集中在 XML 的內容上。清單 1 用 JDOM API 實現了在線商店示例中表示訂單的類中的 toXML()(請參閱 圖 1)。

清單 1. Order 類的 toXML() 的 JDOM 實現

public Element toXML()

{

Element elOrder = new Element("order");

elOrder.setAttribute("id",id);

elOrder.setAttribute

("cost",getFormattedCost());

Element elDate =

new Element("date").addContent(date);

elOrder.addContent(elDate);

Element elItems = new Element("items");

for (Iterator iter =

items.iterator() ; iter.hasNext() ; )

{

elItems.addContent(iter.next().toXML());

}

elOrder.addContent(elItems);

return elOrder;

}
在這裡可以看到用 JDOM 創建元素、使用屬性和添加元素內容有多麼簡單。遞歸地調用復合 JavaBean 的 toXml() 方法是為了取得它們子圖的 Element 表示。例如,items 元素的內容是通過調用 Order 聚合的每個 Item 對象上的 toXML() 得到的。

一旦所有的 JavaBean 都實現了 toXml() 方法,那麼把任意對象圖序列化成 XML 文檔並返回給 AJax 客戶機就簡單了,如清單 2 所示。

清單 2. 從 JDOM 元素生成 XML 響應

public void doGet(HttpServletRequest req, 

HttpServletResponse res)

throws Java.io.IOException,

ServletException

{

String custId =

req.getParameter("username");

Customer customer =

getCustomer(custId);

Element responseElem =

customer.toXML();

Document responseDoc =

new Document(responseElem);

res.setContentType("application/XML");

new XMLOutputter().output

(responseDoc,res.getWriter());

}
JDOM 再次把工作變得非常簡單。只需要在對象圖返回的 XML 元素外面包裝一個 Document,然後用 XMLOutputter 把文檔寫入 servlet 響應即可。清單 3 顯示了用這種方式生成的 XML 示例,用 JDOM Format.getPrettyFormat() 對 XMLOutputter進行初始化,格式化得非常好。在這個示例中,顧客只做了一個訂單,包含兩個商品。

清單 3. 代表顧客的 XML 文檔


encoding="UTF-8"?>



James Hyrax




cost="$349.98">

08-26-2005





Oolong 512MB

CF Card


512 Megabyte Type 1

CompactFlash card.

Manufactured by Oolong

IndustrIEs


$49.99





Fujak Superpix72 Camera

7.2 Megapixel digital

camera featuring six

shooting modes and 3x

optical zoom. Silver.


$299.99



行序列化的不足

有趣的是,清單 3 中的代碼展示了讓 JavaBean 把自己序列化為 XML 的一個主要不足。假設要用這個文檔表示顧客的訂單歷史視圖。在這種情況下,不太可能要顯示每個歷史訂單中每個商品的完整說明,或者告訴顧客他或她自己的姓名。

但是如果應用程序有一個 ProductSearch 類,它就是以 Item bean 列表的形式返回搜索結果,那麼在 Item 的 XML 表示中包含說明可能會有幫助。

而且,Item 類上代表當前庫存水平的額外字段,在產品搜索視圖中可能就是需要顯示的有用信息。但是,不管當前的庫存水平是否與當前情況相關(比如對顧客的訂單歷史來說),這個字段都會從包含 Item 的任何對象圖中序列化出來。

從設計的角度來看,這是數據模型與視圖生成耦合的經典問題。每個 bean 只能用一種途徑序列化自己,一成不變的方式意味著 AJax 交互最終要交換它們不需要交換的數據,因此造成客戶端代碼要從文檔中找到需要的信息更加困難,而且也會增加帶寬消耗和客戶端的 XML 解析時間。

這種耦合的另一個後果就是 XML 的語法不能脫離 Java 類獨立變化。例如,對顧客文檔的方案做修改,可能會影響多個 Java 類,造成它們也不得不做修改和重新編譯。

我稍後會解決這些問題,但是首先來看一個對自行序列化方式的可伸縮性問題的解決方案:XML 綁定框架。 

XML 綁定框架



近些年來,已經開發了多個 Java API 來簡化 XML 文檔到 Java 對象圖的綁定過程。多數都提供了 XML 編排和拆解;也就是說,它們可以在 Java 對象圖和 XML 之間執行雙向會話。

這些框架封裝了 XML 處理的全部工作,這意味著應用程序代碼只需要處理普通的 Java 類。它們還希望提供有用的輔助功能,例如文檔驗證。籠統來說,這些框架采用了兩種不同的方式:代碼生成和對象到 XML 映射。我將分別解釋這兩種方式。

代碼生成方式



使用代碼生成的框架包括 XMLBeans、JAXB、Zeus 和 JBind。Castor 也能使用這項技術。這類框架的起點是描述文檔數據類型的 XML 方案。使用框架提供的工具,就可以生成代表這些方案定義類型的 Java 類。最後,用這些生成的類編寫應用程序,表示自己的模型數據,並通過框架提供的一些輔助機制把數據序列化成 XML。

如果應用程序要使用大型 XML 語法,那麼代碼生成方式是個很好的方法。在數十個類上編寫定制 XML 序列化代碼的可伸縮性問題由此消除。另一方面,也不再需要定義自己的 JavaBean。

框架生成的 Java 類通常非常符合 XML 的結構,所以對它們進行編碼很難。而且,生成的類變成啞數據容器,因為一般不能向它們添加行為。

一般來說,在應用程序代碼中要做些妥協,才能很好地處理方案生成的類型。另一個缺陷是如果修改方案,會造成生成的類也要修改,所以也就會對圍繞它們編寫的代碼帶來相應的影響。

這種類型的 XML 綁定框架在數據拆解時最有用(例如,使用 XML 文檔並把它們轉化成 Java 對象)。除非擁有大型數據模型而且有可能從生成的類中獲益,否則基於代碼生成的框架對於 AJax 應用程序來說可能有很大的殺傷力。

映射方式



采用映射方式的框架包括 Castor 和 apache Commons Betwixt。映射通常是比代碼生成更靈活和更輕量的解決方案。首先,可以像通常一樣編寫 JavaBean,包括任何行為以及任何自己喜歡的方便的方法。

然後,在運行時,調用框架中基於內省的編排器,並根據對象成員的類型、名稱和值生成 XML 文檔。通過定義類的映射文件,可以覆蓋默認的綁定策略,並就類在 XML 中的表示方式對編排器提出建議。

這種方法是在可伸縮性與靈活性之間的良好折中。可以按照自己喜歡的方式編寫 Java 類,編排器負責處理 XML。雖然映射定義文件編寫起來簡單,可伸縮性也足夠好,但是映射規則最多只能改變標准的綁定行為,而且在對象結構和它們的 XML 表示之間總要殘留一些耦合。最終,可能不得不在 Java 表示或 XML 格式之間任選一個做些折中,才能讓映射方法起作用。

數據綁定總結

Dennis Sosnoski 就 XML 數據綁定 API 的主題,在代碼生成和代碼映射兩個方面寫了深入的文章。如果想進一步研究這個領域,我推薦他在 Castor 和代碼生成框架方面的精彩文章。

總之,代碼生成方式損失了過多的靈活性和方便性,對於典型的 AJax 應用程序用處不大。另一方面,基於映射的框架可能工作得很好,但是要恰到好處地調整它們的映射策略,以便從對象生成需要的 XML。

所有的 XML 綁定 API 都具有手工序列化技術的一個主要不足:模型和視圖的耦合。被限制為一個類型一個 XML 表示,就意味著在網絡上總要有冗余數據傳輸。

更嚴重的問題是,在情況要求客戶端代碼使用專門視圖時,客戶端代碼卻無法得到它,所以可能要費力地處理給定對象圖的一成不變的視圖。

在傳統的 Web 應用程序開發中,采用頁面模板系統把視圖生成與控制器邏輯和模型數據干淨地分離。這種方法在 AJax 場景中也會有幫助。

頁面模板系統

任何通用目的的頁面模板技術都可以用來生成 XML,從而使 AJax 應用程序根據自己的數據模型生成任何 XML 響應文檔。

額外收獲是:模板可以用簡單的、表現力強的標記語言編寫,而不是用一行行的 Java 代碼編寫。清單 5 是一個 JSP 頁面,采用了 Customer bean 並表示出定制的 XML 視圖,適合客戶端代碼生成訂單歷史組件。

清單 4. 生成訂單歷史文檔的 JSP

<%@ page contentType="application/xml" %><%@ taglib uri="http://java.sun.com/jsp/jstl/core"

prefix="c" %>

value="${requestScope.customer}"/>

"${cust.username}">

"${cust.orders}">

"${order.formattedCost}">

${order.date}



"${order.items}">



escapeXML="true"/>

${item.formattedPrice}

這個簡潔的模板只輸出訂單歷史視圖需要的數據,不輸出不相關的資料(例如商品說明)。創建產品搜索視圖的定制 XML 應當同樣簡單,這個視圖包含每個商品的完整說明和庫存水平。

模板的問題

另一方面,現在我需要為每個不同視圖創建一個新 JSP,而不能僅僅把需要的對象圖組織起來並序列化它。從設計的角度來說,許多人可能會有爭議,認為這無論如何是件好事,因為這意味著正式地考慮服務器要生成的文檔類型。而且,因為我現在要處理通用的模板環境,而不是特定於 XML 的 API,所以確保標記匹配、元素和屬性的順序正確以及 XML 實體(例如 < 或 &)正確轉義就成了我的責任。

JSP 的核心 out 標記使後面這項工作變得很容易,但是不是所有的模板技術都提供了這樣的機制。最後,沒有方便的途徑可以在服務器端根據方案檢驗生成的 XML 文檔的正確性,但這畢竟不是要在生產環境中做的事,可以方便地在開發期間處理它。

不用 XML 的響應數據

迄今為止,我介紹的所有技術都用 XML 文檔的形式生成服務器響應。但是,XML 有一些問題。其中一個就是延遲。浏覽器不能立即解析 XML 文檔並生成 DOM 模型,所以這會降低某些 AJax 組件需要的“迅捷”感,特別是在較慢的機器上解析大型文檔的時候更是如此。

“現場搜索”就是一個示例,在這種搜索中,當用戶輸入搜索術語時,就會從服務器提取搜索結果並顯示給用戶。對於現場搜索組件來說,迅速地響應輸入是非常重要的,但是同時它還需要迅速而持續地解析服務器的響應。

延遲是一個重要的考慮因素,但是避免使用 XML 的最大原因是差勁的客戶端 DOM API。清單 5 顯示了使用跨浏覽器兼容的方式通過 DOM 得到某個值的時候,通常不得不面對的困難。

清單 5. 在 JavaScript 中導航 XML 響應文檔



// Find name of first item

in customers last order

var orderHistoryDoc = req.responseXML;

var orders =

orderHistoryDoc.getElementsByTagName("order");

var lastOrder =

orders[orders.length - 1];

var firstItem =

lastOrder.getElementsByTagName("item")[0];

var itemNameElement =

firstItem.firstChild;

var itemNameText =

itemNameElement.firstChild.data;



當元素中間存在空白時,情況就變得更加復雜,因為每個元素的 firstChild 經常是個空白文本節點。

現在有 JavaScript 庫可以緩解處理 XML 文檔的麻煩。這些庫包括 Sarissa 和 Google-AJaxSLT,這兩個庫都把 XPath 功能添加到了大多數浏覽器中。

但是,想想替代方案還是值得的。除了 responseXML 之外,XMLHttpRequest 對象還提供了名為 responseText 的屬性,這個屬性只是以字符串的方式提供服務器的響應體。

responseText 屬性

當服務器需要向客戶機發送非常簡單的值時,responseText 特別方便,它可以避免 XML 導致的帶寬支出和處理支出。例如,簡單的 true/false 響應可以由服務器以純文本方式返回,可以是逗號分隔的簡單的名稱或數字列表。

但是,一般來說,最好不要在同一個應用程序中把 XML 響應和純文本響應混合使用;保持單一數據格式可以讓代碼抽象和重用更加簡單。

responseText 與 XML 響應數據結合時也會有用。在只需要從響應文檔中提取單一值的場景中,“欺騙性”地把 XML 當作文本字符串,而不把它當作結構化的文檔對待,會更方便。

例如,清單 6 顯示了如何用正則表達式從顧客的訂單歷史中提取第一筆訂單的日期。不過,這實際是種花招,一般不應當依賴 XML 文檔的詞匯表達。

清單 6. 用正則表達式處理 XMLHttpRequest 的 responseText 對象



var orderHistoryText =

req.responseText;

var matches =

orderHistoryText.match

(/(.*?)<\/date>/);

var date = matches[1];

在某些情況下,采用即時方式使用 responseText 會比較方便。但是,理想情況下,應當有種途徑,可以用一種能夠讓 JavaScript 輕松導航、卻沒有 XML 處理支出的格式表示復雜的結構化數據。幸運的是,確實存在這樣一種格式。

JavaScript 對象標注

實際上,Javascript 對象的大部分都由聯合數組、數字索引數組、字符串、數字或者這些類型的嵌套組合而成。因為所有類型都可以用 JavaScript 直接聲明,所以可以在一條語句中靜態地定義對象圖。

清單 7 使用 JSON 語法聲明了一個對象,並演示了如何訪問這個對象。大括號表示聯合數組(即對象),它的鍵 -值組合由逗號分隔。方括號表示數字索引數組。

清單 7. 用 JSON 在 JavaScript 中直接聲明一個簡單對象



var band =

{

name: "The Beatles",

members: [

{

name: "John",

instruments:

["Vocals","Guitar","Piano"]

},

{

name: "Paul",

instruments:

["Vocals","Bass","Piano","Guitar"]

},

{

name: "George",

instruments:

["Guitar","Vocals"]

},

{

name: "Ringo",

instruments:

["Drums","Vocals"]

}

]

};

// Interrogate the band object

var musician = band.members[3];

alert( musician.name

+ " played " +

musician.instruments[0]

+ " with " +

band.name );



既然 JSON 是一個有趣的語言特性,那麼它對 Ajax 有什麼意義呢?妙處在於可以用 JSON 在 AJax 服務器響應中通過網絡發送 JavaScript 對象圖。

這意味著在客戶端可以避免使用笨拙的 DOM API 對 XML 進行導航 —— 只需要分析 JSON 響應,就會立即得到可以訪問的 Javascript 對象圖。但是,首先需要把 JavaBean 變成 JSON。

從 Java 類產生 JSON



不同 XML 生成技術所具有的優缺點也適用於 JSON 的產生。而且可以證明,存在需要再次使用表示模板技術的情況。但是,使用 JSON 在理念上更接近於在應用層之間傳遞序列化的對象,而不是創建應用程序狀態的視圖。

我將介紹如何用 org.JSon 這個 Java API 在 Java 類上創建 toJSONObject() 方法。然後,就可以把 JSONObject 簡單地序列化成 JSON。清單 8 反映了 清單 1 討論的 XML,顯示了 Order 類的 toJSONObject() 實現。

清單 8. Order 類的 toJSONObject() 方法實現



public JSONObject toJSONObject()

{

JSONObject json = new JSONObject();

JSon.put("id",id);

JSon.put("cost",getFormattedCost());

JSon.put("date",date);

JSONArray jsonItems = new JSONArray();

for (Iterator iter =

items.iterator() ; iter.hasNext() ; )

{

jsonItems.put(iter.next().toJSONObject());

}

json.put("items",JSonItems);

return JSon;

}



可以看到,org.json API 非常簡單。 JSONObject 代表 JavaScript 對象(即聯合數組),有不同的 put() 方法,方法接受的 String 鍵和值是原生類型、String 類型或其他 JSON 類型。

JSONArray 代表索引數組,所以它的 put() 方法只接受一個值。請注意在清單 8 中,創建 jsonItems 數組,然後再用 put() 把它附加到 JSon 對象上;可以用另外一種方法做這項工作,就是對每個項目調用:



JSon.accumulate("items",

iter.next().toJSONObject());

accumulate() 方法與 put()類似,區別在於它把值添加到按照鍵進行識別的索引數組。清單 9 顯示了如何序列化 JSONObject 並把它寫入 servlet 響應。

清單 9. 從 JSONObject 生成序列化的 JSON 響應



public void doGet(HttpServletRequest req,

HttpServletResponse res)

throws Java.io.IOException,

ServletException

{

String custId =

req.getParameter("username");

Customer customer =

getCustomer(custId);

res.setContentType("application/x-JSon");

res.getWriter().print

(customer.toJSONObject());

}

可以看到,它實際上什麼也沒有做。在這裡隱式調用的 JSONObject 的 toString() 方法做了所有工作。請注意,application/x-json 內容類型還有一點不確定 —— 在編寫這篇文章的時候,關於 JSON 應當屬於什麼 MIME 類型還沒有定論。但是,目前 application/x-JSon 是合理的選擇。清單 10 顯示了這個 servlet 代碼的示例響應。

清單 10. Customer bean 的 JSON 表示

{

"orders":

[

{

"items":

[

{

"price": "$49.99",

"description": "

512 Megabyte Type 1 CompactFlash card.

Manufactured by Oolong IndustrIEs",

"name": "Oolong 512MB CF Card",

"id": "i-55768"

},

{

"price": "$299.99",

"description": "

7.2 Megapixel digital camera featuring six

shooting modes and 3x optical zoom. Silver.",

"name": "Fujak Superpix72 Camera",

"id": "i-74491"

}

],

"date": "08-26-2005",

"cost": "$349.98",

"id": "o-11123"

}

],

"realname": "James Hyrax",

"username": "jimmy66"

}

在客戶端使用 JSON

處理的最後一步是把在客戶端把 JSON 數據變成 Javascript 對象。這可以通過對 eval() 的簡單調用實現,這個函數可以即時地解釋包含 JavaScript 表達式的字符串。

清單 11 把 JSON 響應轉變成 JavaScript 對象圖,然後執行清單 5 的任務,從顧客的最後一次訂單中得到第一個商品的名稱。

清單 11. 評估 JSON 響應



var JSonExpression =

"(" + req.responseText + ")";

var customer = eval(JSonExpression);

// Find name of first

item in customers last order

var lastOrder = customer.orders

[customer.orders.length-1];

var name = lastOrder.items[0].name;


比較清單 11 和 清單 5 可以發現使用 JSON 的客戶端的優勢。如果在 AJax 項目中要在客戶端對許多復雜的服務器響應進行導航,那麼 JSON 可能適合您的需要。

JSON 和 XMLHttpRequest 結合還會讓 AJax 交互看起來更像 RPC 調用而不是 SOA 請求,這對應用程序的設計可能會有意義。

JSON 的不足

JSON 也有它的不足。使用這裡介紹的 JSON 方式,就沒有辦法針對每個請求對對象的序列化進行裁剪,所以不需要的字段可能經常會在網絡上發送。

另外,添加 toJSONObject() 方法到每個 JavaBean,可伸縮性不太好,雖然用內省和標注編寫一個通用的 JavaBean 到 JSON 的序列化器可能很簡單。最後,如果服務器端代碼是面向服務的,沒有單獨針對處理 AJax 客戶請求調整過,那麼由於對 XML 一致的支持,XML 會是更好的選擇。

比較序列化技術

現在已經看到了把 Java 狀態傳輸到 AJax 客戶端的五種不同技術。我討論了自行手工編碼 XML 序列化、通過代碼生成的 XML 綁定、通過映射機制的 XML 綁定、基於模板的 XML 生成以及手工編碼到 JSON 的序列化。

每種技術都有自己的優勢和不足,分別適用於不同的應用程序架構。為了總結每種方式的優勢與不足,表 1 從六個方面進行了粗略的評分:

可伸縮性



描述技術適應大量數據類型的容易程度。對於每個附加類型,編碼和配置工作量是否會增長?

易於集成

評估把技術集成到項目的簡單程度。是否需要更加復雜的構建過程?是否增加了部署的復雜性?

Java 類 API

描述以指定方式處理服務器端 Java 對象的容易程度。是可以編寫普通的 bean,還是不得不處理笨拙的文檔表示?

對輸出的控制

描述對類的序列化表示控制的精確程度。

視圖靈活性

評估從同一組對象是否可以創建不同的、定制的數據序列化。

客戶端數據訪問

描述 JavaScript 代碼處理服務器響應數據的難易程度。

結束語

表 1 中的數據並不表明某項序列化技術比其他的技術好。畢竟,六種標准的相對重要性取決於項目的具體情況。例如,如果要處理數百種數據類型,這時想要的是可伸縮性,那麼代碼生成可能就是最好的選擇。

如果需要為同一數據模型生成多個不同視圖,那麼就應當使用頁面模板。如果處理的是小規模項目,想降低需要編寫的JavaScript代碼數量,那麼請考慮JSON。希望這篇文章為您提供了選擇適合自己應用程序的序列化技術所需要的信息

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved