多數(shù) Java 開發(fā)人員已經(jīng)把模型-視圖-控制器(MVC)模式應(yīng)用在他們的 Web 應(yīng)用程序上。在傳統(tǒng)的 Web 應(yīng)用程序中,視圖組件由 JSP 或者其他表示技術(shù)(例如 Velocity 模板)構(gòu)成。
這些表示組件動(dòng)態(tài)地生成全新的 HTML 頁面,替代用戶以前正在查看的頁面,從而更新用戶界面。但是,在 Java Web 應(yīng)用程序使用 Ajax UI 的情況下,基于從 XMLHttpRequest 的響應(yīng)接收到的數(shù)據(jù),JavaScript 客戶端代碼對(duì)于更新用戶看到的內(nèi)容負(fù)有最終責(zé)任。從服務(wù)器的角度來看,視圖成為它響應(yīng)客戶機(jī)請(qǐng)求而發(fā)送的數(shù)據(jù)表示。
本文側(cè)重于可以用來生成 Java 對(duì)象以數(shù)據(jù)為中心的視圖的技術(shù)。我將演示可以把 JavaBeans 變成 XML 文檔的各種方法,并且討論每種方法的優(yōu)劣。您將看到為什么 XML 并不總是最好的途徑:對(duì)于簡單的 Ajax 請(qǐng)求來說,傳輸純文本更好。
最后,我還將介紹 JavaScript 對(duì)象標(biāo)注(JSON)。JSON 允許數(shù)據(jù)以序列化的 JavaScript 對(duì)象圖的形式傳輸,在客戶端代碼中處理序列化的 JavaScript 對(duì)象圖極為容易。
關(guān)于示例
我將使用一個(gè)示例應(yīng)用程序和幾個(gè)用例來演示這里討論的技術(shù)特性和技術(shù)。圖 1 顯示的極為簡單的數(shù)據(jù)模型可以表示示例用例。這個(gè)模型代表在線商店中的顧客帳戶。顧客擁有以前訂單的集合,每個(gè)訂單包含幾個(gè)商品。
雖然 XMLHttpRequest 對(duì)于發(fā)送數(shù)據(jù)使用的格式?jīng)]有做任何限制,但是對(duì)于多數(shù)目的來說,只發(fā)送傳統(tǒng)的表單數(shù)據(jù)是適合的,所以我的討論集中在服務(wù)器的響應(yīng)上。
響應(yīng)也可以有基于文本的格式,但是正如它的名字表示的,XMLHttpRequest 具有內(nèi)置的處理 XML 響應(yīng)數(shù)據(jù)的能力。這使 XML 成為 Ajax 響應(yīng)的默認(rèn)選擇,所以我們從 XML 格式開始討論。
從 Java 類產(chǎn)生 XML
把 Ajax 響應(yīng)作為 XML 來傳遞有許多原因:每個(gè)支持 Ajax 的瀏覽器都有導(dǎo)航 XML 文檔的方法,也有許多服務(wù)器端技術(shù)可以處理 XML 數(shù)據(jù)。
通過制定一個(gè)方案,描述要交換的文檔類型,在 Ajax 客戶端和服務(wù)器端之間很容易定義合約,而且如果服務(wù)器端架構(gòu)采用面向服務(wù)的方式,那么使用 XML 也可以允許非 Ajax 客戶機(jī)使用您提供的數(shù)據(jù)。
我將考慮從 Java 對(duì)象產(chǎn)生 XML 數(shù)據(jù)的三種方法,并討論每種方法的優(yōu)劣。
自行進(jìn)行序列化
首先,可以從對(duì)象圖以編程的方式生成 XML。這種方式可以簡單到只是在每個(gè) JavaBean 類中實(shí)現(xiàn) toXml() 方法即可。然后就可以選擇合適的 XML API,讓每個(gè) bean 提供表示自己狀態(tài)的元素,并遞歸地對(duì)自己的成員調(diào)用對(duì)象圖。
顯然,這種方式無法擴(kuò)展到大量的類,因?yàn)槊總(gè)類都需要專門編寫自己的 XML 生成代碼。從好的方面來看,這是一個(gè)實(shí)現(xiàn)起來簡單的方式,沒有額外的配置支出或者更復(fù)雜的構(gòu)建過程支出,任何 JavaBean 圖都可以只用幾個(gè)調(diào)用就變成 XML 文檔。
我曾把XML標(biāo)記字符串連接在一起,實(shí)現(xiàn)了toXml()方法。上次我就提到過,這是個(gè)糟糕的方法,因?yàn)樗汛_保標(biāo)記配對(duì)、實(shí)體編碼等工作的負(fù)擔(dān)放在每個(gè) toXml() 方法的代碼中。
在 Java 平臺(tái)上有幾個(gè) XML API 可以替您做這些工作,這樣您就可以把精力集中在 XML 的內(nèi)容上。清單 1 用 JDOM API 實(shí)現(xiàn)了在線商店示例中表示訂單的類中的 toXml()(請(qǐng)參閱 圖 1)。
清單 1. Order 類的 toXml() 的 JDOM 實(shí)現(xiàn)
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 創(chuàng)建元素、使用屬性和添加元素內(nèi)容有多么簡單。遞歸地調(diào)用復(fù)合 JavaBean 的 toXml() 方法是為了取得它們子圖的 Element 表示。例如,items 元素的內(nèi)容是通過調(diào)用 Order 聚合的每個(gè) Item 對(duì)象上的 toXml() 得到的。
一旦所有的 JavaBean 都實(shí)現(xiàn)了 toXml() 方法,那么把任意對(duì)象圖序列化成 XML 文檔并返回給 Ajax 客戶機(jī)就簡單了,如清單 2 所示。
清單 2. 從 JDOM 元素生成 XML 響應(yīng)
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 再次把工作變得非常簡單。只需要在對(duì)象圖返回的 XML 元素外面包裝一個(gè) Document,然后用 XMLOutputter 把文檔寫入 servlet 響應(yīng)即可。清單 3 顯示了用這種方式生成的 XML 示例,用 JDOM Format.getPrettyFormat() 對(duì) XMLOutputter進(jìn)行初始化,格式化得非常好。在這個(gè)示例中,顧客只做了一個(gè)訂單,包含兩個(gè)商品。
清單 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 的一個(gè)主要不足。假設(shè)要用這個(gè)文檔表示顧客的訂單歷史視圖。在這種情況下,不太可能要顯示每個(gè)歷史訂單中每個(gè)商品的完整說明,或者告訴顧客他或她自己的姓名。
但是如果應(yīng)用程序有一個(gè) ProductSearch 類,它就是以 Item bean 列表的形式返回搜索結(jié)果,那么在 Item 的 XML 表示中包含說明可能會(huì)有幫助。
而且,Item 類上代表當(dāng)前庫存水平的額外字段,在產(chǎn)品搜索視圖中可能就是需要顯示的有用信息。但是,不管當(dāng)前的庫存水平是否與當(dāng)前情況相關(guān)(比如對(duì)顧客的訂單歷史來說),這個(gè)字段都會(huì)從包含 Item 的任何對(duì)象圖中序列化出來。
從設(shè)計(jì)的角度來看,這是數(shù)據(jù)模型與視圖生成耦合的經(jīng)典問題。每個(gè) bean 只能用一種途徑序列化自己,一成不變的方式意味著 Ajax 交互最終要交換它們不需要交換的數(shù)據(jù),因此造成客戶端代碼要從文檔中找到需要的信息更加困難,而且也會(huì)增加帶寬消耗和客戶端的 XML 解析時(shí)間。
這種耦合的另一個(gè)后果就是 XML 的語法不能脫離 Java 類獨(dú)立變化。例如,對(duì)顧客文檔的方案做修改,可能會(huì)影響多個(gè) Java 類,造成它們也不得不做修改和重新編譯。
我稍后會(huì)解決這些問題,但是首先來看一個(gè)對(duì)自行序列化方式的可伸縮性問題的解決方案:XML 綁定框架。
XML 綁定框架
近些年來,已經(jīng)開發(fā)了多個(gè) Java API 來簡化 XML 文檔到 Java 對(duì)象圖的綁定過程。多數(shù)都提供了 XML 編排和拆解;也就是說,它們可以在 Java 對(duì)象圖和 XML 之間執(zhí)行雙向會(huì)話。
這些框架封裝了 XML 處理的全部工作,這意味著應(yīng)用程序代碼只需要處理普通的 Java 類。它們還希望提供有用的輔助功能,例如文檔驗(yàn)證;\統(tǒng)來說,這些框架采用了兩種不同的方式:代碼生成和對(duì)象到 XML 映射。我將分別解釋這兩種方式。
代碼生成方式
使用代碼生成的框架包括 XMLBeans、JAXB、Zeus 和 JBind。Castor 也能使用這項(xiàng)技術(shù)。這類框架的起點(diǎn)是描述文檔數(shù)據(jù)類型的 XML 方案。使用框架提供的工具,就可以生成代表這些方案定義類型的 Java 類。最后,用這些生成的類編寫應(yīng)用程序,表示自己的模型數(shù)據(jù),并通過框架提供的一些輔助機(jī)制把數(shù)據(jù)序列化成 XML。
如果應(yīng)用程序要使用大型 XML 語法,那么代碼生成方式是個(gè)很好的方法。在數(shù)十個(gè)類上編寫定制 XML 序列化代碼的可伸縮性問題由此消除。另一方面,也不再需要定義自己的 JavaBean。
框架生成的 Java 類通常非常符合 XML 的結(jié)構(gòu),所以對(duì)它們進(jìn)行編碼很難。而且,生成的類變成啞數(shù)據(jù)容器,因?yàn)橐话悴荒芟蛩鼈兲砑有袨椤?
一般來說,在應(yīng)用程序代碼中要做些妥協(xié),才能很好地處理方案生成的類型。另一個(gè)缺陷是如果修改方案,會(huì)造成生成的類也要修改,所以也就會(huì)對(duì)圍繞它們編寫的代碼帶來相應(yīng)的影響。
這種類型的 XML 綁定框架在數(shù)據(jù)拆解時(shí)最有用(例如,使用 XML 文檔并把它們轉(zhuǎn)化成 Java 對(duì)象)。除非擁有大型數(shù)據(jù)模型而且有可能從生成的類中獲益,否則基于代碼生成的框架對(duì)于 Ajax 應(yīng)用程序來說可能有很大的殺傷力。
映射方式
采用映射方式的框架包括 Castor 和 Apache Commons Betwixt。映射通常是比代碼生成更靈活和更輕量的解決方案。首先,可以像通常一樣編寫 JavaBean,包括任何行為以及任何自己喜歡的方便的方法。
然后,在運(yùn)行時(shí),調(diào)用框架中基于內(nèi)省的編排器,并根據(jù)對(duì)象成員的類型、名稱和值生成 XML 文檔。通過定義類的映射文件,可以覆蓋默認(rèn)的綁定策略,并就類在 XML 中的表示方式對(duì)編排器提出建議。
這種方法是在可伸縮性與靈活性之間的良好折中?梢园凑兆约合矚g的方式編寫 Java 類,編排器負(fù)責(zé)處理 XML。雖然映射定義文件編寫起來簡單,可伸縮性也足夠好,但是映射規(guī)則最多只能改變標(biāo)準(zhǔn)的綁定行為,而且在對(duì)象結(jié)構(gòu)和它們的 XML 表示之間總要?dú)埩粢恍詈。最終,可能不得不在 Java 表示或 XML 格式之間任選一個(gè)做些折中,才能讓映射方法起作用。
數(shù)據(jù)綁定總結(jié)
Dennis Sosnoski 就 XML 數(shù)據(jù)綁定 API 的主題,在代碼生成和代碼映射兩個(gè)方面寫了深入的文章。如果想進(jìn)一步研究這個(gè)領(lǐng)域,我推薦他在 Castor 和代碼生成框架方面的精彩文章。
總之,代碼生成方式損失了過多的靈活性和方便性,對(duì)于典型的 Ajax 應(yīng)用程序用處不大。另一方面,基于映射的框架可能工作得很好,但是要恰到好處地調(diào)整它們的映射策略,以便從對(duì)象生成需要的 XML。
所有的 XML 綁定 API 都具有手工序列化技術(shù)的一個(gè)主要不足:模型和視圖的耦合。被限制為一個(gè)類型一個(gè) XML 表示,就意味著在網(wǎng)絡(luò)上總要有冗余數(shù)據(jù)傳輸。
更嚴(yán)重的問題是,在情況要求客戶端代碼使用專門視圖時(shí),客戶端代碼卻無法得到它,所以可能要費(fèi)力地處理給定對(duì)象圖的一成不變的視圖。
在傳統(tǒng)的 Web 應(yīng)用程序開發(fā)中,采用頁面模板系統(tǒng)把視圖生成與控制器邏輯和模型數(shù)據(jù)干凈地分離。這種方法在 Ajax 場(chǎng)景中也會(huì)有幫助。
頁面模板系統(tǒng)
任何通用目的的頁面模板技術(shù)都可以用來生成 XML,從而使 Ajax 應(yīng)用程序根據(jù)自己的數(shù)據(jù)模型生成任何 XML 響應(yīng)文檔。
額外收獲是:模板可以用簡單的、表現(xiàn)力強(qiáng)的標(biāo)記語言編寫,而不是用一行行的 Java 代碼編寫。清單 5 是一個(gè) JSP 頁面,采用了 Customer bean 并表示出定制的 XML 視圖,適合客戶端代碼生成訂單歷史組件。
清單 4. 生成訂單歷史文檔的 JSP
value="${requestScope.customer}"/>
"${cust.username}"> "${cust.orders}"> "${order.formattedCost}"> ${order.date}
"${order.items}">
escapeXml="true"/>${item.formattedPrice}
這個(gè)簡潔的模板只輸出訂單歷史視圖需要的數(shù)據(jù),不輸出不相關(guān)的資料(例如商品說明)。創(chuàng)建產(chǎn)品搜索視圖的定制 XML 應(yīng)當(dāng)同樣簡單,這個(gè)視圖包含每個(gè)商品的完整說明和庫存水平。
模板的問題
另一方面,現(xiàn)在我需要為每個(gè)不同視圖創(chuàng)建一個(gè)新 JSP,而不能僅僅把需要的對(duì)象圖組織起來并序列化它。從設(shè)計(jì)的角度來說,許多人可能會(huì)有爭議,認(rèn)為這無論如何是件好事,因?yàn)檫@意味著正式地考慮服務(wù)器要生成的文檔類型。而且,因?yàn)槲椰F(xiàn)在要處理通用的模板環(huán)境,而不是特定于 XML 的 API,所以確保標(biāo)記匹配、元素和屬性的順序正確以及 XML 實(shí)體(例如 < 或 &)正確轉(zhuǎn)義就成了我的責(zé)任。
JSP 的核心 out 標(biāo)記使后面這項(xiàng)工作變得很容易,但是不是所有的模板技術(shù)都提供了這樣的機(jī)制。最后,沒有方便的途徑可以在服務(wù)器端根據(jù)方案檢驗(yàn)生成的 XML 文檔的正確性,但這畢竟不是要在生產(chǎn)環(huán)境中做的事,可以方便地在開發(fā)期間處理它。
不用 XML 的響應(yīng)數(shù)據(jù)
迄今為止,我介紹的所有技術(shù)都用 XML 文檔的形式生成服務(wù)器響應(yīng)。但是,XML 有一些問題。其中一個(gè)就是延遲。瀏覽器不能立即解析 XML 文檔并生成 DOM 模型,所以這會(huì)降低某些 Ajax 組件需要的“迅捷”感,特別是在較慢的機(jī)器上解析大型文檔的時(shí)候更是如此。
“現(xiàn)場(chǎng)搜索”就是一個(gè)示例,在這種搜索中,當(dāng)用戶輸入搜索術(shù)語時(shí),就會(huì)從服務(wù)器提取搜索結(jié)果并顯示給用戶。對(duì)于現(xiàn)場(chǎng)搜索組件來說,迅速地響應(yīng)輸入是非常重要的,但是同時(shí)它還需要迅速而持續(xù)地解析服務(wù)器的響應(yīng)。
延遲是一個(gè)重要的考慮因素,但是避免使用 XML 的最大原因是差勁的客戶端 DOM API。清單 5 顯示了使用跨瀏覽器兼容的方式通過 DOM 得到某個(gè)值的時(shí)候,通常不得不面對(duì)的困難。
清單 5. 在 JavaScript 中導(dǎo)航 XML 響應(yīng)文檔
// 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;
當(dāng)元素中間存在空白時(shí),情況就變得更加復(fù)雜,因?yàn)槊總(gè)元素的 firstChild 經(jīng)常是個(gè)空白文本節(jié)點(diǎn)。
現(xiàn)在有 JavaScript 庫可以緩解處理 XML 文檔的麻煩。這些庫包括 Sarissa 和 Google-ajaXSLT,這兩個(gè)庫都把 XPath 功能添加到了大多數(shù)瀏覽器中。
但是,想想替代方案還是值得的。除了 responseXML 之外,XMLHttpRequest 對(duì)象還提供了名為 responseText 的屬性,這個(gè)屬性只是以字符串的方式提供服務(wù)器的響應(yīng)體。
responseText 屬性
當(dāng)服務(wù)器需要向客戶機(jī)發(fā)送非常簡單的值時(shí),responseText 特別方便,它可以避免 XML 導(dǎo)致的帶寬支出和處理支出。例如,簡單的 true/false 響應(yīng)可以由服務(wù)器以純文本方式返回,可以是逗號(hào)分隔的簡單的名稱或數(shù)字列表。
但是,一般來說,最好不要在同一個(gè)應(yīng)用程序中把 XML 響應(yīng)和純文本響應(yīng)混合使用;保持單一數(shù)據(jù)格式可以讓代碼抽象和重用更加簡單。
responseText 與 XML 響應(yīng)數(shù)據(jù)結(jié)合時(shí)也會(huì)有用。在只需要從響應(yīng)文檔中提取單一值的場(chǎng)景中,“欺騙性”地把 XML 當(dāng)作文本字符串,而不把它當(dāng)作結(jié)構(gòu)化的文檔對(duì)待,會(huì)更方便。
例如,清單 6 顯示了如何用正則表達(dá)式從顧客的訂單歷史中提取第一筆訂單的日期。不過,這實(shí)際是種花招,一般不應(yīng)當(dāng)依賴 XML 文檔的詞匯表達(dá)。
清單 6. 用正則表達(dá)式處理 XMLHttpRequest 的 responseText 對(duì)象
var orderHistoryText = req.responseText; var matches = orderHistoryText.match (/(.*?)<\/date>/);
var date = matches[1];
在某些情況下,采用即時(shí)方式使用 responseText 會(huì)比較方便。但是,理想情況下,應(yīng)當(dāng)有種途徑,可以用一種能夠讓 JavaScript 輕松導(dǎo)航、卻沒有 XML 處理支出的格式表示復(fù)雜的結(jié)構(gòu)化數(shù)據(jù)。幸運(yùn)的是,確實(shí)存在這樣一種格式。 JavaScript 對(duì)象標(biāo)注
實(shí)際上,JavaScript 對(duì)象的大部分都由聯(lián)合數(shù)組、數(shù)字索引數(shù)組、字符串、數(shù)字或者這些類型的嵌套組合而成。因?yàn)樗蓄愋投伎梢杂?JavaScript 直接聲明,所以可以在一條語句中靜態(tài)地定義對(duì)象圖。
清單 7 使用 JSON 語法聲明了一個(gè)對(duì)象,并演示了如何訪問這個(gè)對(duì)象。大括號(hào)表示聯(lián)合數(shù)組(即對(duì)象),它的鍵 -值組合由逗號(hào)分隔。方括號(hào)表示數(shù)字索引數(shù)組。
清單 7. 用 JSON 在 JavaScript 中直接聲明一個(gè)簡單對(duì)象
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 是一個(gè)有趣的語言特性,那么它對(duì) Ajax 有什么意義呢?妙處在于可以用 JSON 在 Ajax 服務(wù)器響應(yīng)中通過網(wǎng)絡(luò)發(fā)送 JavaScript 對(duì)象圖。
這意味著在客戶端可以避免使用笨拙的 DOM API 對(duì) XML 進(jìn)行導(dǎo)航 —— 只需要分析 JSON 響應(yīng),就會(huì)立即得到可以訪問的 JavaScript 對(duì)象圖。但是,首先需要把 JavaBean 變成 JSON。
從 Java 類產(chǎn)生 JSON
不同 XML 生成技術(shù)所具有的優(yōu)缺點(diǎn)也適用于 JSON 的產(chǎn)生。而且可以證明,存在需要再次使用表示模板技術(shù)的情況。但是,使用 JSON 在理念上更接近于在應(yīng)用層之間傳遞序列化的對(duì)象,而不是創(chuàng)建應(yīng)用程序狀態(tài)的視圖。
我將介紹如何用 org.json 這個(gè) Java API 在 Java 類上創(chuàng)建 toJSONObject() 方法。然后,就可以把 JSONObject 簡單地序列化成 JSON。清單 8 反映了 清單 1 討論的 XML,顯示了 Order 類的 toJSONObject() 實(shí)現(xiàn)。
清單 8. Order 類的 toJSONObject() 方法實(shí)現(xiàn)
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 對(duì)象(即聯(lián)合數(shù)組),有不同的 put() 方法,方法接受的 String 鍵和值是原生類型、String 類型或其他 JSON 類型。 JSONArray 代表索引數(shù)組,所以它的 put() 方法只接受一個(gè)值。請(qǐng)注意在清單 8 中,創(chuàng)建 jsonItems 數(shù)組,然后再用 put() 把它附加到 json 對(duì)象上;可以用另外一種方法做這項(xiàng)工作,就是對(duì)每個(gè)項(xiàng)目調(diào)用:
json.accumulate("items", iter.next().toJSONObject());
accumulate() 方法與 put()類似,區(qū)別在于它把值添加到按照鍵進(jìn)行識(shí)別的索引數(shù)組。清單 9 顯示了如何序列化 JSONObject 并把它寫入 servlet 響應(yīng)。
清單 9. 從 JSONObject 生成序列化的 JSON 響應(yīng)
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()); }
可以看到,它實(shí)際上什么也沒有做。在這里隱式調(diào)用的 JSONObject 的 toString() 方法做了所有工作。請(qǐng)注意,application/x-json 內(nèi)容類型還有一點(diǎn)不確定 —— 在編寫這篇文章的時(shí)候,關(guān)于 JSON 應(yīng)當(dāng)屬于什么 MIME 類型還沒有定論。但是,目前 application/x-json 是合理的選擇。清單 10 顯示了這個(gè) servlet 代碼的示例響應(yīng)。
清單 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 數(shù)據(jù)變成 JavaScript 對(duì)象。這可以通過對(duì) eval() 的簡單調(diào)用實(shí)現(xiàn),這個(gè)函數(shù)可以即時(shí)地解釋包含 JavaScript 表達(dá)式的字符串。
清單 11 把 JSON 響應(yīng)轉(zhuǎn)變成 JavaScript 對(duì)象圖,然后執(zhí)行清單 5 的任務(wù),從顧客的最后一次訂單中得到第一個(gè)商品的名稱。
清單 11. 評(píng)估 JSON 響應(yīng)
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 可以發(fā)現(xiàn)使用 JSON 的客戶端的優(yōu)勢(shì)。如果在 Ajax 項(xiàng)目中要在客戶端對(duì)許多復(fù)雜的服務(wù)器響應(yīng)進(jìn)行導(dǎo)航,那么 JSON 可能適合您的需要。
JSON 和 XMLHttpRequest 結(jié)合還會(huì)讓 Ajax 交互看起來更像 RPC 調(diào)用而不是 SOA 請(qǐng)求,這對(duì)應(yīng)用程序的設(shè)計(jì)可能會(huì)有意義。在下一篇文章中,我要研究的框架,就是明確地為了讓 JavaScript 代碼對(duì)服務(wù)器端對(duì)象進(jìn)行遠(yuǎn)程方法調(diào)用而設(shè)計(jì)的。 JSON 的不足
JSON 也有它的不足。使用這里介紹的 JSON 方式,就沒有辦法針對(duì)每個(gè)請(qǐng)求對(duì)對(duì)象的序列化進(jìn)行裁剪,所以不需要的字段可能經(jīng)常會(huì)在網(wǎng)絡(luò)上發(fā)送。
另外,添加 toJSONObject() 方法到每個(gè) JavaBean,可伸縮性不太好,雖然用內(nèi)省和標(biāo)注編寫一個(gè)通用的 JavaBean 到 JSON 的序列化器可能很簡單。最后,如果服務(wù)器端代碼是面向服務(wù)的,沒有單獨(dú)針對(duì)處理 Ajax 客戶請(qǐng)求調(diào)整過,那么由于對(duì) XML 一致的支持,XML 會(huì)是更好的選擇。
比較序列化技術(shù)
現(xiàn)在已經(jīng)看到了把 Java 狀態(tài)傳輸?shù)?Ajax 客戶端的五種不同技術(shù)。我討論了自行手工編碼 XML 序列化、通過代碼生成的 XML 綁定、通過映射機(jī)制的 XML 綁定、基于模板的 XML 生成以及手工編碼到 JSON 的序列化。
每種技術(shù)都有自己的優(yōu)勢(shì)和不足,分別適用于不同的應(yīng)用程序架構(gòu)。為了總結(jié)每種方式的優(yōu)勢(shì)與不足,表 1 從六個(gè)方面進(jìn)行了粗略的評(píng)分:
可伸縮性
描述技術(shù)適應(yīng)大量數(shù)據(jù)類型的容易程度。對(duì)于每個(gè)附加類型,編碼和配置工作量是否會(huì)增長?
易于集成
評(píng)估把技術(shù)集成到項(xiàng)目的簡單程度。是否需要更加復(fù)雜的構(gòu)建過程?是否增加了部署的復(fù)雜性?
Java 類 API
描述以指定方式處理服務(wù)器端 Java 對(duì)象的容易程度。是可以編寫普通的 bean,還是不得不處理笨拙的文檔表示?
對(duì)輸出的控制
描述對(duì)類的序列化表示控制的精確程度。
視圖靈活性
評(píng)估從同一組對(duì)象是否可以創(chuàng)建不同的、定制的數(shù)據(jù)序列化。
客戶端數(shù)據(jù)訪問
描述 JavaScript 代碼處理服務(wù)器響應(yīng)數(shù)據(jù)的難易程度。
結(jié)束語
表 1 中的數(shù)據(jù)并不表明某項(xiàng)序列化技術(shù)比其他的技術(shù)好。畢竟,六種標(biāo)準(zhǔn)的相對(duì)重要性取決于項(xiàng)目的具體情況。例如,如果要處理數(shù)百種數(shù)據(jù)類型,這時(shí)想要的是可伸縮性,那么代碼生成可能就是最好的選擇。
如果需要為同一數(shù)據(jù)模型生成多個(gè)不同視圖,那么就應(yīng)當(dāng)使用頁面模板。如果處理的是小規(guī)模項(xiàng)目,想降低需要編寫的JavaScript代碼數(shù)量,那么請(qǐng)考慮JSON。希望這篇文章為您提供了選擇適合自己應(yīng)用程序的序列化技術(shù)所需要的信息。
|