通過 GWT 可以方便地訪問用 Java 語言編寫的服務器端 servlet,並且客戶機和服務器之間的數據傳遞是透明的。但是,當使用 GWT 時,不僅可以與那些 servlet 通信,還可以隨意地與所有類型的 Web 服務交換數據。在很多情況下(對於簡單的服務),可以用純文本格式傳輸數據,但是每當遇到結構化的數據或較復雜的數據(例如 RSS)時,數據很可能是用 XML 表示的。
本文研究一個簡單的 GWT 應用程序和兩個 PHP Web 服務,展示生成和使用 XML 文檔的幾種不同的方法。本文無意成為詳盡的教程或手冊,而是提供一些忠告,使您能更快地開始使用 XML 作為連接 GWT 與 PHP 的橋梁。
測試應用程序
為了展示如何使用 XML 作為 PHP 與 GWT 之間的橋梁,我提供一個簡單的應用程序,這個應用程序基於國家/地區/城市數據。查看 清單 1 中的數據庫創建代碼,可以看到:
國家(country)有惟一的代碼(例如 UY 表示烏拉圭)和名稱。
國家 被劃分為多個地區(region),地區有(國家內惟一的)代碼和名稱。
地區 內有多個城市(city),城市有(純 ASCII)名稱、別名(可能包括外來字符)、人口(如果未知則為 0)、緯度和經度。城市名可出現在同一個國家的不同地區。
清單 1. 數據庫創建代碼
CREATE DATABASE world
DEFAULT CHARACTER SET latin1
COLLATE latin1_general_ci;
USE world;
CREATE TABLE countrIEs (
countryCode char(2) NOT NULL,
countryName varchar(50) NOT NULL,
PRIMARY KEY (countryCode)
KEY countryName (countryName)
);
CREATE TABLE regions (
countryCode char(2) NOT NULL,
regionCode char(2) NOT NULL,
regionName varchar(50) NOT NULL,
PRIMARY KEY (countryCode,regionCode),
KEY regionName (regionName)
);
CREATE TABLE citIEs (
countryCode char(2) NOT NULL,
cityName varchar(50) NOT NULL,
cityAccentedName varchar(50) NOT NULL,
regionCode char(2) NOT NULL,
population bigint(20) NOT NULL,
latitude float(10,7) NOT NULL,
longitude float(10,7) NOT NULL,
KEY `INDEX` (countryCode,regionCode,cityName),
KEY cityName (cityName),
KEY cityAccentedName (cityAccentedName)
);
我創建了一個簡單的 GWT 項目,它只有一個表單和兩個 PHP Web 服務。(下載 小節提供了完整的源代碼)。啟動該應用程序時,可以看到 圖 1 中的窗口。
圖 1. 空的表單
圖片看不清楚?請點擊這裡查看原圖(大圖)。
GWT 表單允許輸入一個城市名的一部分,然後調用一個 PHP 服務獲得與輸入的內容匹配的所有城市。這些城市顯示在一個網格中,可以編輯人口(population)、緯度(latitude)和經度(longitude)字段。然後,可以將編輯的數據發回到另一個 PHP Web 服務,後者將更新數據庫。所有數據傳輸都是通過 XML 進行的。2009 年是查爾斯達爾文 200 誕辰和他的著作物種起源 誕生 150 周年,您可以查看城市名中包含 DARWIN 的城市;圖 2 顯示了結果。
圖 2. 搜索城市名中包含 “Darwin” 的城市
圖片看不清楚?請點擊這裡查看原圖(大圖)。
一些額外的配置
下面是我使用的軟件,僅供參考:
GWT version 1.5.3
PHP version 5.2.8
MySQL® database server version 5.0.67
apache version 2.2.10 under OpenSUSE® version 11.1
我安裝的所有軟件都是開箱即用的,但是 GWT 要求一個額外的配置步驟,這樣才能測試 GWT-PHP 連接;請參閱側邊欄 SOP 問題,看看是什麼原因。為了禁用內部 GWT 浏覽器的同源策略(same-origin policy,SOP)檢查,可以編輯 GWT 目錄中的 ./mozilla-1.7.12/greprefs/all.JS 文件,在文件的最後添加 清單 2 中的代碼行:
清單 2. 修改內部 GWT 浏覽器的配置
pref("capability.policy.default.XMLHttpRequest.abort", "allAccess");
pref("capability.policy.default.XMLHttpRequest.getAllResponseHeaders","allAccess");
pref("capability.policy.default.XMLHttpRequest.getResponseHeader","allAccess");
pref("capability.policy.default.XMLHttpRequest.open", "allAccess");
pref("capability.policy.default.XMLHttpRequest.send", "allAccess");
pref("capability.policy.default.XMLHttpRequest.setRequestHeader","allAccess");
pref("capability.policy.default.XMLHttpRequest.onreadystatechange","allAccess");
pref("capability.policy.default.XMLHttpRequest.readyState", "allAccess");
pref("capability.policy.default.XMLHttpRequest.responseText","allAccess");
pref("capability.policy.default.XMLHttpRequest.responseXML","allAccess");
pref("capability.policy.default.XMLHttpRequest.status", "allAccess");
pref("capability.policy.default.XMLHttpRequest.statusText", "allAccess");
每當更新 GWT 時,需要再次作出這樣的更改。而且,請注意,如果不這樣做,編寫的代碼在 Hosted 模式下會失敗,但是在 compiled 模式下卻可以正確運行。作出以上更改後,您的代碼可能在 Hosted 模式下可以運行,但是在 Compiled 模式下卻會失敗,所以要小心!
用 PHP 發送 XML
這個應用程序只定義一個簡單的表單,其中有一些標簽、一個文本框、兩個命令按鈕和一個用於顯示結果的網格。每當單擊 Get citIEs 時,該應用程序調用一個 PHP 服務,以便獲得一個 XML 文檔,其中包含與文本框中輸入的內容匹配的所有城市。清單 3 顯示從 PHP 服務發送到 GWT 應用程序的一個示例 XML(有所簡化)。生成的 XML 代碼要用於演示一些 XML 功能,所以比用於真正的應用程序的 XML 要長得多。 通常,設計良好的 XML 服務使用更少的標記和更多的屬性,避免縮進,並且更短一些。
清單 3. 搜索 “tokyo” 時生成的 XML 文檔
<?XML version="1.0" encoding="UTF-8"?>
<citIEs>
<city name="tokyo">
<country code="JP" name="Japan"/>
<region code="40" name="Tokyo"/>
<coords>
<lat>35.6850014</lat>
<lon>139.7513885</lon>
</coords>
<pop>31480498</pop>
</city>
<city name="tokyo">
<country code="PG" name="Papua New Guinea"/>
<region code="01" name="Central"/>
<coords>
<lat>-8.3999996</lat>
<lon>147.1499939</lon>
</coords>
</city>
<city name="tokyojitori">
<country code="KR" name="Korea, Republic of"/>
<region code="16" name="Cholla-namdo"/>
<coords>
<lat>34.2380562</lat>
<lon>125.9394455</lon>
</coords>
</city>
</citIEs>
這個 PHP 服務本身有兩個版本 — getcities1.php 和 getcitIEs2.PHP —,每個版本展示生成 XML 的不同方法。
到目前為止,生成 XML 的最簡單的方法是連續輸出適當的文本,或者構建一個字符串,然後使用 echo 發出它。這裡應該將 content type 設為 text/xml,使之能夠被正確地識別,並且還要記得包括一個適當的 description 行,以指定 XML 版本和數據編碼方式。另外還必須轉義字符串,使字符串中不包括小於(<)、大於(>)或和(&)字符。最簡單的方法是使用 Htmlspecialchars() PHP 函數。代碼很容易編寫,如 清單 4 所示。注意,縮進和換行實際上是不需要的,不過這樣做可以讓代碼更易於閱讀。
清單 4. 從 PHP 服務生成 XML 的最簡單的方法
...
header("Content-type: text/XML");
...
echo '<?XML version="1.0" encoding="UTF-8"?>'."\n";
echo '<citIEs>'."\n";
...
while ($row= MySQL_fetch_assoc($result)) {
echo ' <city name="'.Htmlspecialchars($row['cityName']).'">'."\n";
echo ' <country code="'.$row['countryCode'].'" ';
echo 'name="'.HtmlentitIEs($row['countryName']).'"/>'."\n";
echo ' <region code="'.$row['regionCode'].'" ';
echo 'name="'.HtmlentitIEs($row['regionName']).'"/>'."\n";
echo ' <coords>'."\n";
echo ' <lat>'.$row['latitude'].'</lat>'."\n";
echo ' <lon>'.$row['longitude'].'</lon>'."\n";
echo ' </coords>'."\n";
if ($row['population']>0) {
echo ' <pop>'.$row['population'].'</pop>'."\n";
}
echo ' </city>'."\n";
}
echo '</citIEs>'."\n";
生成 XML 代碼的另一種方法(也不是很長)是使用 XMLWriter。(與之成對的另一個類 XMLReader 可用於 XML 處理)。現在可以不用關心字符的轉義,因為這是自動完成的。雖然這種方法看起來比前面的 echo() 方法冗長一點,但是這種方法編寫的代碼更易於理解。特別注意,這裡使用了 PHP://output 協議,以便用 echo 命令發出文本。(如 清單 5 所示)。
清單 5. XMLWriter 提供逐個元素地構建 XML 文檔的簡單方法
...
$writer= new XMLWriter();
$writer->openURI('PHP://output')
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement("citIEs");
while ($row= MySQL_fetch_assoc($result)) {
$writer->startElement("city");
$writer->writeAttribute("name", $row['cityName']);
$writer->startElement("country");
$writer->writeAttribute("code", $row['countryCode']);
$writer->writeAttribute("name", $row['countryName']);
$writer->endElement();
$writer->startElement("region");
$writer->writeAttribute("code", $row['regionCode']);
$writer->writeAttribute("name", $row['regionName']);
$writer->endElement();
$writer->startElement("coords");
$writer->writeElement("lat", $row['latitude']);
$writer->writeElement("lon", $row['longitude']);
$writer->endElement();
if ($row['population']>0) {
$writer->writeElement("pop", $row['population']);
}
$writer->endElement(); // city
}
$writer->endElement(); // citIEs
...
如果想體驗更多生成 XML 的方法,PHP 當然提供了很多的選擇。例如,可以使用 SimpleXML;以後您將使用它讀取 XML,但是它還提供 XML 文檔創建功能。另外,還可以看一下 PEAR 框架,它包括一些可用於輕松生成 XML 的類。(請參閱 參考資料,獲得更多信息的鏈接)。
用 GWT 處理 XML
GWT 只提供 XMLParser(在 com.google.gwt.xml.clIEnt 包中)用於讀、寫 XML。可以使用 parse() 方法創建一個 Document,然後使用 getDocumentElement() 獲得它的根元素;然後,便可以開始處理 XML。
注意,應該使用 removeWhitespace() 方法去掉文檔中的空白。浏覽器解析器有時候會創建與制表符或換行符對應的空文本節點,如果不去掉它們,處理過程中會遇到不相關的、意外的元素,這樣會破壞邏輯(見 清單 6)。另外還有一點要注意:如果預期有 CDATA 區段,那麼需要檢查浏覽器是否接受 supportsCDATASection() 方法;如果不接受,那些區段將產生文本節點。請參閱 GWT 文檔(見 參考資料),獲得更多這方面的信息。
清單 6. GWT 中提供了 XMLParser 用於讀取和創建 XML
protected void loadCitIEs(final String XMLCitIEs) {
...
final Document xmlDoc= XMLParser.parse(XMLCitIEs);
final Element root= XMLDoc.getDocumentElement();
XMLParser.removeWhitespace(XMLDoc);
final NodeList citIEs= root.getElementsByTagName("city");
for (int i= 0; i < citIEs.getLength(); i++) {
final Element city= (Element)citIEs.item(i);
// show city.getAttributeNode("name").getValue()
final Element country= (Element)city.getElementsByTagName("country").item(0);
// show country.getAttributeNode("code").getValue()
// show country.getAttributeNode("name").getValue()
...
final Element population= (Element)city.getElementsByTagName("pop").item(0);
if (population != null) {
// show population.getFirstChild().getNodeValue()
}
final Element coords= (Element)city.getElementsByTagName("coords").item(0);
final Element lat= (Element)coords.getElementsByTagName("lat").item(0);
// show lat.getFirstChild().getNodeValue()
...
}
...
要獲得重復的元素(例如這個例子中的 city),可以使用 getElementsByTagName() 方法,並迭代生成的數組。另一種方法是使用 getFirstChild() 方法,然後使用 getNextSibling() 遍歷同一級別上的其他元素。要獲得屬性,首先需要使用 getAttributeNode() 方法,然後使用 getValue() 方法。還有一些用於處理 CDATA 區段、注釋和所有可能的 XML 組件的方法。
用 GWT 發送 XML
GWT 應用程序讓用戶編輯人口、緯度和經度字段,然後將城市數據發回到服務器,以更新數據庫。有兩種算法:一種是簡單的算法,這種算法逐塊構建 XML 字符串,還有一種基於 XMLParser 的算法,該算法使用特定的方法創建所需的結構。
getCitIEs1() 方法中顯示了較簡單的算法。可以使用 String 或 StringBuffer 對象構建 XML。我使用了前者,因為它使代碼更加清晰;但是如果考慮性能,後者很可能更好一些。這裡也存在和 PHP 版本中一樣的字符串轉義問題,所以使用 Html.Htmlspecialchars() 方法修改該問題。(見 清單 7,為了看起來更清晰,稍微作了修改)。
清單 7. 使用字符串逐塊構建 XML 很簡單
protected String getCitIEs1() {
String result= "";
result+= "<?XML version=\"1.0\" encoding=\"UTF-8\"?>\n";
result+= "<citIEs>\n";
for (all rows in the grid) {
// get cityName, countryCode, regionCode, pop, lat, and lon, from the grid
result+= " <city name=\"" + Html.Htmlspecialchars(cityName) + "\">\n";
result+= " <country code=\"" + countryCode + "\"/>\n";
result+= " <region code=\"" + regionCode + "\"/>\n";
if (!pop.equals("0") && !pop.isEmpty()) {
result+= " <pop>" + pop + "</pop>\n";
}
result+= " <coords>\n";
result+= " <lat>" + lat + "</lat>\n";
result+= " <lon>" + lon + "</lon>\n";
result+= " </coords>\n";
result+= "</city>\n";
}
result+= "</citIEs>\n";
return result;
}
另一種創建 XML 的簡單算法是 XMLParser 類的 createDocument() 方法。這種算法的風格很容易讓人想起 PHP 中的 SimpleXML 函數,首先創建一個空文檔,然後向其中添加元素。可以創建所有類型的節點,並設置屬性值。最後,標准的 Java toString() 方法生成對象的一個表示。您只需添加初始版本和編碼行,就可以得到需要的字符串。(見 清單 8,為了看起來更清晰,代碼有所修改和刪減)。
清單 8. 創建 XML 的 XMLParser 方法類似於 PHP 中的 SimpleXML 方法
protected String getCitIEs2() {
Document xml= XMLParser.createDocument();
Element citIEs= XML.createElement("citIEs");
XML.appendChild(citIEs);
for (all rows in the grid) {
// get cityName, countryCode, regionCode, pop, lat, and lon, from the grid
Element city= XML.createElement("city");
city.setAttribute("name", cityName);
Element country= XML.createElement("country");
country.setAttribute("code", countryCode);
city.appendChild(country);
...
if (!pop.equals("0") && !pop.isEmpty()) {
Element popEl= XML.createElement("pop");
Text popText= XML.createTextNode(pop);
popEl.appendChild(popText);
city.appendChild(popEl);
}
Element coords= XML.createElement("coords");
Element lat= XML.createElement("lat");
Text latText= XML.createTextNode(lat);
lat.appendChild(latText);
coords.appendChild(lat);
...
city.appendChild(coords);
citIEs.appendChild(city);
}
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + XML.toString();
}
在 PHP 中讀取 XML
在 PHP 中處理 XML 是一個老問題,有很多的解決方案。但是,我認為,從清晰和簡潔的角度看,沒有哪種方案能比得上 SimpleXML 。基本上,首先通過將 XML 文檔提供給 simpleXML_load_string() 方法創建一個 PHP 對象,然後使用標准的 PHP 操作符遍歷結果。屬性變成數組中的元素(例如,清單 9 展示了如何讀取國家代碼),而元素則可以作為一個數組(通過使用 children() 方法)或作為對象的屬性來訪問。
清單 9. SimpleXML 是目前在 PHP 中進行 XML 處理的最容易的方法
$xml_str= $_POST["XMLdata"];
$xml_obj= simplexml_load_string($XML_str);
...
foreach($XML_obj->children() as $city) {
$name= addslashes($city['name']);
$country= $city->country['code'];
$region= $city->region['code'];
$pop= $city->pop;
$lat= $city->coords->lat;
$lon= $city->coords->lon;
MySQL_query("REPLACE INTO citIEs ".
"(cityName, countryCode, regionCode, population, latitude, longitude) VALUES (".
"'{$name}', '{$country}', '{$region}', '{$pop}', '{$lat}', '{$lon}')");
}
在 PHP 中處理 XML 還有許多種方法,可以從 參考資料 小節找到相關的鏈接。
結束語
有很多方法可以使用 XML 作為 GWT 與 PHP 之間的橋梁,我只是略作探討,不過,本文給出的方法應該足以讓您邁出第一步。需要記住的要點是,GWT 不止局限於它自己的 RPC 方法,還可以與 XML 友好共存,輕松生成和使用 XML 文檔。
下載
描述 名字 大小 下載方法 本文的 Java 源代碼 Java_source_code.zip 4KB HTTP 本文的 PHP 源代碼 PHP_source_code.zip 4KB HTTP