XML:GWT 与 PHP 之间的桥梁
 

2009-06-25 作者:Federico Kereki 来源:IBM

 
本文内容包括:
Google Web Toolkit(GWT)应用程序除了以传统的 Java™ 方式连接到 servlet 外,还可以使用 PHP Web 服务发送和接收 XML 格式的数据。您将探索以 Java 和 PHP 语言生成和处理 XML 文档的方法。

通过 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() 方法中显示了较简单的算法。可以使用 StringStringBuffer 对象构建 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
本文的 PHP 源代码 php_source_code.zip
4KB

参考资料

学习 获得产品和技术 讨论

火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织