簡介
Web 2.0 日益流行在很大程度上是由於當代 Web 浏覽器有很強的靈活性。這些浏覽器使用 AJax 與服務器通信來交換 XML 數據,然後利用 DHtml 在 XML 中導航並將數據顯示給用戶。浏覽器的強大功能使它們能夠作為基於 Web 的應用程序中的瘦客戶機。
以這些技術為基礎,產生了許多新技術和項目來滿足 Web 2.0 的需要。新的輔助庫(比如 Dojo 和 Sarissa)可以簡化使用 XML 的過程,還增加了對 Scalable Vector Graphics(SVG)的支持。為了滿足 Web 2.0 的需要,還開發了可嵌入的小部件(比如 Google 和 Yahoo Maps)、數據聯合和集成方法(比如 RSS 和 Atom feed)以及使用數據 mashup 的新型開發方式。
如圖 1 所示,連接所有這些技術的都是 XML。提要中的 XML 使提要能夠通過網絡發布和接收復雜的數據結構。DHtml 中的 DOM 應用程序編程接口(API)和 Sarissa 中的 XPath 支持可以在客戶機中執行高效的 XML 導航,從而進行讀寫操作。
圖 1. 面向 Web 的體系結構
注意:在以提要和服務形式查看數據源的 Web 2.0 環境中,數據庫驅動程序擴展為支持 REST、FEED 和 SOAP 調用。如果解決了安全隱患,就能夠從應用服務器和 Web 客戶機使用 SOAP 或 REST 調用直接訪問數據庫例程,而不必創建不必要的映射。在下一篇文章中,將為 DB2® 創建一個 SOAP 驅動程序示例。
在客戶機中使用 XML 模型
因為許多 XML 周邊技術在應用服務器和客戶機上都存在,所以也可以將 第一篇文章 中的 XML 數據模型擴展到客戶機。在本文中,學習如何擴展 XML 模型,從而使用來自應用服務器的 XML 數據創建功能豐富的客戶機。了解如何使用 DHtml 顯示 XML 數據,用 XPath 進行導航,用 DOM 修改 XML 數據、序列化並返回給應用服務器。
與在應用服務器中創建的 DOM 包裝器相似,我們將為客戶機創建一個 DOM 包裝器。這個包裝器不但將 Javascript 顯示和業務邏輯代碼與 DOM API 隔離開,還使代碼與應用服務器層中的 Java ™ 代碼非常相似。為此,我們將使用開放源碼庫 Sarissa,這個庫為浏覽器中運行的 JavaScript 代碼提供了 XPath API。本文後面提供了包裝器代碼(見 下載)。
<script type="text/Javascript" src="sarissa/sarissa.JS"></script>
<script language="Javascript" type="text/Javascript" src="XMLparse.JS"></script>
場景
下面幾節繼續使用本系列前幾篇文章中的保險示例。Web 客戶機提供以下功能。
1. 允許用戶查看和修改個人信息中的名字和姓氏。
2. 根據用戶選擇的保險公司,計算購買的每個商品的保險費率。
一定要注意:本文和前幾篇文章中的代碼只用於解釋概念。只有解釋 XML 模型的部分編寫了完整的代碼。邏輯的其他部分是偽代碼,讀者可以推導出功能所需的標准代碼。下一篇文章討論一個完整的業務場景,包含完整的代碼。
允許用戶查看和修改他們的姓名。
從應用服務器獲得客戶信息並在客戶機上顯示。
客戶機 JavaScript 代碼
profilediv 用來將從 XML 數據創建的 Html 字符串顯示給用戶。
<div class="profilediv" id="profilediv"></div>
當用戶登錄站點時,輸入客戶 id(cid)。當用戶在客戶機中選擇 Update profile 時,將這個客戶 id 傳遞給 getCustomerInfo 函數。客戶機使用 Sarissa 庫提供的 XMLHttpRequest API 向服務器發送一個 HTTP 請求。在這個請求中,還定義回調函數。當服務器產生響應時,會調用函數 customerInfoCallback。清單 1 演示如何獲取客戶信息:
清單 1. 客戶機請求客戶信息<script>
function getCustomerInfo(cid)
{
var xmlhttpObj= new XMLHttpRequest();
var addr=hosturl+"?cmd= getuserprofile&msg="+cid;
var xmlhttpObj= new XMLHttpRequest();
XMLhttpObj.open('GET', addr, true);
xmlhttpObj.onreadystatechange = function() { customerInfoCallback(XMLhttpObj); };
XMLhttpObj.send("");
}
應用服務器 Java 代碼
清單 2 顯示當應用服務器接收客戶機 HTTP 請求時發生的情況。服務器首先檢查 HTTP 請求是 POST 還是 GET,然後根據這個信息,從 HTTP 調用的參數獲得命令和數據值。
清單 2. 應用服務器接收請求 public void service( HttpServletRequest _req, HttpServletResponse _res)
throws ServletException, IOException
{
String cmd, msgtext, returnvalue;
if(_req.getMethod().equalsIgnoreCase("POST"))
{
String message = getPostBody(_req.getReader());
XMLParse msgxml=new XMLParse(message);
cmd= msgXML.getValue("//request/@cmd");
msgtext= msgXML.toString("/request/*");
}
else
{
cmd= _req.getParameter("cmd");
msgtext= _req.getParameter("msg");
}
因為命令是請求獲得用戶的個人信息,所以應用服務器訪問數據庫中的客戶信息,並將信息返回給客戶機。清單 3 只顯示獲得此信息所需的 SQL 語句;讀者應該知道如何使用 Java Database Connectivity(JDBC)API 連接數據庫並獲得查詢結果。
因為客戶信息在數據庫中存儲為 XML 文檔,所以在將它返回給客戶機之前不需要執行任何轉換。注意,在 HTTP 報頭中,返回數據的內容類型設置為 XML。
清單 3. 應用服務器的響應 if(cmd.equalsIgnoreCase("getuserprofile"))
{
//returnvalue= select CUSTXML from customer_table where customerid =msgtext
}
_res.setContentType("text/XML");
_res.getWriter().write(returnvalue);
_res.setHeader("Cache-Control", "no-cache");
客戶機 JavaScript 代碼
當客戶機收到應用服務器的響應時,調用 callback 事件中定義的函數。
清單 4 顯示對 userinfo 的請求。注意,userinfo 聲明為全局變量,因為當發送用戶對姓名的更新時要修改這個變量。
清單 4. 對 userinfo 的請求 var userinfo=null;
function customerInfoCallback (XMLhttp)
{
if (xmlhttp.readyState == 4 && XMLhttp.status == 200)
{
因為服務器返回的數據是 XML,所以將它直接傳遞給 DOM 包裝器的構造器。
userinfo= new xmlparse(xmlhttp.responseXML, false);
接下來,使用 XPath 從 XML 中提取出姓名。
var firstname = userinfo.getValue("/Customer/@firstname",null);
var lastname = userinfo.getValue("/Customer/@lastname",null);
清單 5 演示如何使用 DHtml 創建一個用於修改客戶名稱的圖形用戶界面(GUI),並將它插入 Html 頁面中聲明的 div 標記 profilediv。
清單 5. 創建 GUI 的 DHtml var Htmlstr="<table class='inputtable'><tr>";
Htmlstr+='<td>firstname:</td><td><input id="fname" value="'+firstname+'"/></td>';
Htmlstr+='<td>lastname:</td><td><input id="lname" value="'+lastname+'"/></td>';
Htmlstr+='<tr><td/><td><input type="button" value="save"
/></td>';
Htmlstr+='</tr></table>';
document.getElementById("profilediv").innerHTML=Htmlstr;
}
}
完成修改之後,單擊 Save 按鈕來調用 updateCustomer 函數。用修改信息更新包含客戶信息的 DOM userinfo。這裡同樣使用 XPath 導航到需要修改的數據節點。
清單 6. 使用 XPath 更新客戶信息 function updateCustomer()
{
var fname=document.getElementById("fname").value;
var lname=document.getElementById("lname").value;
userinfo.setValue("/Customer/@firstname",null,fname);
userinfo.setValue("/Customer/@lastname",null,lname);
創建一個新請求,將更新後的 DOM 序列化並附加到請求中。清單 7 顯示發送給(POST)服務器的請求字符串。
注意,因為消息格式是 XML,所以請求的 HTTP 報頭中的內容類型設置為 XML。 清單 7. 發送給服務器的請求字符串 var msg='<request cmd=" updateuserprofile">'+userinfo.toString("/")+'</request>';
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
var xmlhttpObj= new XMLHttpRequest();
XMLhttpObj.open('POST', hostname, true);
xmlhttpObj.onreadystatechange = function() { profileUpdated(XMLhttpObj); };
xmlhttpObj.setRequestHeader('content-type', 'text/XML');
XMLhttpObj.send(msg);
}
應用服務器 Java 代碼
使用 DOM 包裝器解析請求中的客戶 XML 數據。
因為 customerid 是原來發送給客戶機的客戶個人信息中的一個屬性,所以在請求中不需要為客戶 ID 發送額外參數。
清單 8 演示如何從 XML 中提取出 customerid 並用它創建更新數據庫的 SQL 語句。來自 HTTP 請求的客戶個人數據作為參數傳遞給更新語句;不需要修改。
清單 8. 創建 SQL 更新語句 else if(cmd.equalsIgnoreCase("updateuserprofile"))
{
XMLParse custxml=new XMLParse(msgtext);
String cid= custXML.getValue("/Customer/@customerid");
//update customer_table set custXML=? where customerid=cid
//stmt.setString(1,msgtext);
}
注意,在客戶機和服務器中用來導航 XML 的應用程序代碼是相似的。另外,對於兩個 HTTP 請求,應用服務器僅僅作為客戶機和數據庫之間的中介 —— 在交換過程中並不操作數據。
計算購買的商品的保險費率
用戶從一個包含所有保險公司名稱的下拉列表中選擇一家保險公司。保險公司提供一個 Web 服務,可以查詢這個服務來獲得當前的保險費率。費率信息以 XML 文檔的形式提供,應用程序使用此信息計算每種商品的保險費。
客戶機 JavaScript 代碼
還從列表中選擇保險公司提供的 Web 服務的 URL。因為 AJax 不允許 URL 重定向,所以需要從應用服務器調用 Web 服務。將 URL 放在請求中傳遞給應用服務器,請求計算購買的每種商品的保險費。清單 9 演示這個過程:
清單 9. 客戶機選擇 URL function itemsPurchased(url,cid)
{
var msg='<request cmd="getPurchaseInfoWithInsurance"><data customerid="cid">
<![CDATA['+ url+']]></data></request>';
var xmlhttpObj= new XMLHttpRequest();
XMLhttpObj.open('POST', hostname, true);
xmlhttpObj.onreadystatechange = function() { temsPurchasedCallback (XMLhttpObj); };
xmlhttpObj.setRequestHeader('content-type', 'text/XML');
XMLhttpObj.send(msg);
}
}
注意:因為 URL 可能包含特殊字符,所以我們把它嵌入在 CDATA 部分中,以避免請求 XML 變得怪異。
應用服務器 Java 代碼
使用 DOM 包裝器解析請求消息並從其中提取出保險服務 URL。清單 10 演示應用服務器如何使用這個 URL 調用保險公司的 Web 服務,獲取包含保險費率的 XML 文檔。
清單 10. 應用服務器獲取保險費率 else if(cmd.equalsIgnoreCase("getPurchaseInfoWithInsurance"))
{
XMLParse dataxml=new XMLParse(msgtext);
String url= dataXML.toString("/data/text()");
String insurancestr=callWebServiceUsingHTTPClIEnt(url);
注意:在 第 2 部分 中的 “一個更精細的示例” 一節中,定義了保險費率 XML。
再次從消息中提取出客戶 ID,並用它在數據庫中查詢這位客戶購買的商品的相關信息。
String cid= dataXML.getValue("/data/@customerid");
創建購買的商品列表的業務邏輯可以以兩種方式之一實現:
調用 第 2 部分的清單 6 中創建的數據庫存儲過程。 // returnvalue = call customerItemsWithInsurance (cid, insurancestr);
使用第一篇文章中的代碼在應用服務器中編寫邏輯。使用 第 1 部分的清單 6 中的 customerXML,循環遍歷每個商品、計算保險費並添加到商品信息中。
用以下代碼替換原來代碼中的 9-11 行:
清單 11. 替換 9-11 行的代碼 XMLParse insurancexml=new XMLParse(insurancestr);
customerXML.find("/Customer/Items/item",true);
String currency= insuranceXML.getValue("//rate/@currency");
for(int i=0; customerXML.currentFind.getLength()>i;i++)
{
price = customerXML.getValue("@price",i));
if(price>500) rate= insuranceXML.getValue("//rate[@price=""]/@rate"));
else If(price>100) rate= insuranceXML.getValue("//rate[@price="500"]/@rate"));
else rate= insuranceXML.getValue("//rate[@price="100"]/@rate"));
String iteminsurance="<insurance currency="+currency+ ">"+price*rate+"</insurance>"
customerXML. appendElement(customerXML.createNode (iteminsurance),
customerXML.getNode (null,i), false )
}
// returnvalue = customerXML.toString();
如果將上面的代碼與第 2 部分的 customerItemsWithInsurance 存儲過程中的查詢進行對比,就會發現兩者之間有許多相似之處,尤其是使用的 XPath。這再次說明了使用 XML 模型的優點和將業務邏輯放在數據庫中的簡便性。
還要注意 XPath 表達式在搜索和導航層次化 XML 數據模型 方面的能力。如果在對象數據模型 中使用 Java 代碼實現同樣的搜索,就需要做大量工作。使用 XPath 簡化了這個過程,只需使用一個字符串表達式。
客戶機 JavaScript 代碼
服務器調用 itemsPurchasedCallback 函數,在這個函數中使用 DOM 包裝器解析返回的 XML 數據。清單 12 演示具體做法:
清單 12. 使用 DOM 包裝器解析 XML function itemsPurchasedCallback (XMLhttp)
{
if (xmlhttp.readyState == 4 && XMLhttp.status == 200)
{
var itemInfo= new xmlparse(xmlhttp.responseXML, false);
首先從返回的 XML 中提取出客戶名:
清單 13. 提取客戶名 var firstname = userinfo.getValue("/Customer/@firstname",null);
var lastname = userinfo.getValue("/Customer/@lastname",null);
var Htmlstr="<table class='inputtable'>"
Htmlstr+='<tr><td>firstname:<td colspan=5>'+firstname
Htmlstr+='<tr><td>lastname:<td colspan=5>'+lastname
然後,循環遍歷文檔中的所有商品並提取相關信息,創建用來向用戶顯示信息的 HTML 字符串。 清單 14. 生成 Html itemInfo.find("//item",null,true);
Htmlstr+='<tr><td>itemID<td>description<td>date<td>price<td>insurance';
for(var j=0;itemInfo.currentFind.length>j ;j++)
{
var id= itemInfo.getValue("@ID",j);
var description= itemInfo.getValue("@description",j);
var purchaseDate= itemInfo.getValue("@purchaseDate",j);
var price= itemInfo.getValue("@price",j);
var insurance= itemInfo.getValue("insurance/text()",j);
var currency= itemInfo.getValue("insurance/@currency",j);
Htmlstr+='<tr><td>'+id+'<td>'+description+'<td>' +purchaseDate+
'<td>' +currency+price++'<td>' +insurance;
}
document.getElementById("profilediv").innerHTML=Htmlstr;
}
注意,使用 XPath 搜索和遍歷商品元素的客戶端 Javascript 代碼和前面定義的應用服務器 Java 代碼很相似。
對象數據模型
即使使用標准的對象數據模型方法,應用程序層數據對象仍然需要序列化為客戶機可以理解的一種格式。如果對象很復雜,那麼基本上有兩種方式:
1. 在應用程序層中生成客戶機頁面並在 Web 頁面的適當部分中填寫數據(ASP/JSP 方式)。然後公開多個 API 來接受用戶對數據的任何修改。這種方式的問題是:
它將客戶機和應用服務器代碼混在一起,導致一種糟糕的體系結構;
它要求創建多個 API 並提供 API 的文檔記錄,這樣客戶機才能傳遞回對數據的修改。
2. 生成一個數據結構,可以將它作為字符串發送給客戶機。最適合表示這種序列化數據結構的格式是 XML。這種方式的問題是:
必須編寫額外代碼來執行序列化。
如果來自客戶機的更新後數據是 XML,那麼應用服務器中需要用更多代碼向應用程序數據對象解釋修改後的 XML。
如果必須用 XML 數據模型與客戶機進行通信,那麼在應用程序層本身中為什麼不使用 XML 數據模型呢?
我們仍然需要操作保險費率 Web 服務的結果。因為這一信息是 XML 文檔,所以需要先分解它並將信息放到對象模型中。如果數據在數據庫中保存為 XML 格式,就可以將 XML 傳遞給 customerItemsWithInsurance 存儲過程。這種方式的問題是,如果以 XML 格式存儲數據,而在應用程序中使用對象模型,就會產生各種映射和轉換問題。
如果數據在數據庫中存儲為關系形式,對數據庫的查詢會變得更復雜。另外,必須在中間層中編寫更多代碼,將關系數據映射到對象數據模型或 XML 數據模型。
結束語
在面向 Web 的體系結構中,數據需要序列化,然後才能在應用程序的不同層之間傳遞。對於在 Web 客戶機和應用服務器之間傳輸復雜的數據,最合適的方式是 XML。在每一層中都可能需要查詢、轉換和更新數據,如果保持數據的 XML 本質,就可以使用基於 XML 的技術(比如 XPath、DOM 和 XQuery)操作數據。XML 數據模型編程方式可能成為解決業務數據集成問題的萬能方法。
本文示例源代碼或素材下載