级别: 初级 马春娥, 软件工程师, IBM
2009 年 10 月 26 日 Feed 作为一种简单的有标准支持的聚合格式,在越来越多的 Web2.0 的 web application 里面被广泛的用作数据的建模或者是 applications 之间的数据交换格式。像 Google Data,Lotus Connections,Yahoo!都无一例外的选择了Feed。因此,对于 Web 2.0 的 web 应用开发人员来说,处理 Feed 和选择一个合适的 API 非常重要。Feed 本身来说是一个良好定义的自包含的 XML 文档。本文就常用来操作 Feed 的 API: DOM, Abdera, XSLT,XBean 以 query 为例进行示例演示,并且从 API 的易使用性,内存消耗,性能方面进行分析,给正在开发需要 Feed 处理的 web 应用的程序员提供有价值的参考。
DOM 以及 DOM API
在开始使用 DOM 之前,对它究竟表示什么有个大概的了解是有所帮助的。DOM 是 Document Object Model,也就是所谓的文档对象模型。DOM 文档 是以层次结构组织的节点或信息片断的集合。开发人员可以在树中导航仪寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才能做任何工作。由于它是基于信息层次的,因而 DOM 被认为是基于树或基于对象的。详细的 DOM 介绍请参考 w3 规范:Document Object Model (DOM) Level 2 HTML Specification 和文章:“理解 DOM”。下面主要是以 DOM API 的使用为主介绍怎么使用 DOM API 从一个 XML 文档查询所需的信息。下面的代码示例是以 DOM Level2 的标准和实现为基础的。先来看代码清单 1:
清单 1. 用 DOM API 实现 Feed Query
package xml.dom;
import java.io.InputStream;
import java.io.OutputStream;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class DOMFilterFeed implements main.IFilterFeed{
public OutputStream filter(InputStream is, String xpathExpr){
OutputStream os = System.out;
try {
// Create Dom Node Object from input Stream
Node node = this.getDocument(is);
// Create XPath Object using same namespace context
XPath xpathIns = this.createXPath();
// XPath expression
String expression = "atom:feed/atom:entry[not(" + xpathExpr + ")]";
// Perform the XPath evaluation
NodeList nodelist =
(NodeList) xpathIns.evaluate(expression, node ,XPathConstants.NODESET);
for (int i = 0; i <nodelist.getLength(); i++) {
Node filteredNode = nodelist.item(i);
filteredNode.getParentNode().removeChild(filteredNode);
}
//serialize XML dom Node into outputStream
this.serialize(os, node);
} catch (Exception e) {
e.printStackTrace();
}
return os;
}
private Node getDocument(InputStream is) throws Exception{
Node node = null;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setValidating(false);
DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
node = documentBuilder.parse(is);
return node;
}
private XPath createXPath(){
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
NamespaceContext nsctx = new SampleNamespaceContext();
if (nsctx == null) {
nsctx = new SampleNamespaceContext();
}
xpath.setNamespaceContext(nsctx);
return xpath;
}
private void serialize (OutputStream outputStream, Node node) {
try {
final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
DOMSource source = new DOMSource(node);
StreamResult sResult = new StreamResult(outputStream);
transformer.transform(source, sResult);
} catch (TransformerException te) {
throw new RuntimeException(te);
}
}
}
|
其中 main.IFilterFeed 是程序自定义的一个借口。定义了 filter 接口。详细的 code 可以从所附的源代码里面获得。
从上面的代码示例清单 1 可以看出,DOM API 的使用还是比较繁琐的,要实现在一个 feed 里面查找满足条件(XPath Expression 表示)的一些信息,需要很多步骤:
-
从输入的文件解析出 DOM Node.
在这一步,程序需要创建 DocumentBuilderFactory 的实例,然后需要设置一些属性比如设置 DocumentBuilder 是不是 namespace aware 的,以及是否需要按照 XML 规范对输入的 XML 做一些格式的校验。然后由 DocumentBuilderFactory 创建 DocumentBuilder,继而在调用 DocumentBuilder 的 parse 方法创建 DOM Node.
-
因为在第二步我们设置了 parser 是 namespace aware 的,所以接下来,我们需要创建一个 Namespace Context,他实现 javax.xml.namespace.NamespaceContext 的接口,详细的 code 请下载本文附的 src code 里面的 SampleNamespaceContext.
-
创建 XPath 对象,用以执行 query 的操作。
用 XPathFactory 创建 XPath 对象,并把 namespace 设置成第二步里面创建的 SampleNamespaceContext。
-
执行 query 的逻辑。
-
DOM Node 的序列化。
把生成的 DOM Node 序列化到 output stream 或者 writer。本示例使用了 XSLT 作为了完全转换,输入到 output stream。
另一方面,DOM 还提供了一系列 API,允许开发人员添加、编辑、移动或删除树中任意位置的节点,从而创建一个应用程序。
因为 DOM 是基于层次树的操作,所以在执行任何操作前,程序都需要把整个文档加载到内存,并在内存中创建一颗 DOM 树。这对于特别大的文档,解析和加载整个文档可能很慢且很耗资源。但是程序员可以在 DOM 树上任意行走,任意增删改 DOM Node,这也许是 DOM 的一个很大的好处。
Abdera 以及 Abdera API
Abdera是 Apache 的一个 incubation 的项目,旨在提供一个 IETF ATOM(RFC 4287)功能完备的、性能优良的实现。现在 Abdera 已经发布了 0.4.0 版本。
Abdera 创建了自己的 Feed Object Model 也就是 FOM 作为 Feed 的内部数据结构,FOM 为 ATOM 定义了一系列 FOMObject,像 FOMFeed,FOMEntry,FOMLink 等等,提供了自己的 parser, writer, XPath 等一系列 API。要使用 Abdera API,程序需要包含的包有:
* Core API: abdera.core.0.3.0-incubating.jar (required)
* Parser Impl: abdera.parser.0.3.0-incubating.jar (required)
* IRI Support: abdera.i18n.0.3.0-incubating.jar (required)
* axiom-api-1.2.5.jar (required by core)
* axiom-impl-1.2.5.jar (required by core)
* geronimo-activation_1.0.2_spec-1.1.jar (any JAF impl is required)
* commons-logging-1.0.4.jar
* jaxen-1.1.1.jar (required by the core, XPath 实现 )
|
下面还是来看看 Abdera API 的使用情况。清单 2 是用 Abdera API 实现的 Feed Query。
清单 2. 用 Abdera API 实现 Feed Query
package xml.Abdera;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.abdera.Abdera;
import org.apache.abdera.model.Document;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;
import org.apache.abdera.writer.Writer;
import org.apache.abdera.xpath.XPath;
public class AbderaFilterFeed implements main.IFilterFeed{
public OutputStream filter(InputStream is, String xpathExpr){
OutputStream os = System.out;
// new adbera instance
Abdera abdera = Abdera.getNewFactory().getAbdera();
// parse input file stream into Adbera Document object
Document<Feed> doc = abdera.getParser().parse(is);
//get Feed FOM object
Feed feed = doc.getRoot();
//create xpath object
XPath xpath = abdera.getXPath();
//specify the xpath expression
//String expression = "atom:feed/atom:entry[ " + xpathExpr + "]";
String expression =
"atom:feed/atom:entry[atom:content/row:row/row:Customer_Name = 'Ford']";
//generate namespace map
Map<String,String> namespaces = new HashMap<String,String>();
namespaces.put("atom", "http://www.w3.org/2005/Atom");
namespaces.put("row", "http://www.ibm.com/xmlns/atom/content/datarow/1.0");
//select nodes using xpath object
List entries = xpath.selectNodes(expression, feed.getDocument(), namespaces);
//generate output feed
Feed output = abdera.newFeed();
for( int i = 0; i < entries.size(); i++ ){
Entry entry = (Entry) entries.get(i);
output.addEntry(entry);
}
//get pretty xml writer and writer to output stream
Writer writer = abdera.getWriterFactory().getWriter("prettyxml");
try {
writer.writeTo(output, os);
} catch (IOException e) {
e.printStackTrace();
}
return os;
}
}
|
可以看出,用 Abdera API 实现的 Feed Query 基本需要这几步:
-
程序首先需要获得 Abdera 实例,
-
获得相应的 parser 用来解析输入的 xml 文档,
-
接着还是用 Abdera 实例获得单例 XPath 对象,
-
生成相应的 namespace Map,
-
执行 XPath evaluation,
-
序列化。
从以上的步骤看出,和清单 1 中的 DOM API 比起来,从步骤的多少来看基本没有差别,但是每一步 Abdera 都提供了友好的 API,使得程序不需要自己关注那许多底层的对象的创建。比如 namespace 的创建,DOM API 要求程序实现 NamespaceContext 接口,而 Abdera 只需要传入一个 namespace Map 就好了,也没有复杂的属性的设置。而且 Abdera 提供很好的 Serialization 的 API,程序可以根据自己的需要获得相应的 writer,本示例程序是需要在 console 输入程序允许结果,所以用了 prettyxml 的格式输出便于阅读。
XMLBeans 以及 XMLBeans API
XMLBeans是 Apache 的另一个处理 XML 的项目,旨在提供一个直观的方式允许程序访问 XML。通过为 XML 提供类似 java 面向对象的试图,使得熟悉了面向对象的程序员倍感亲切。另外,XMLBeans 还提供了功能强大 XMLCursor, 类似于指针,用 XMLCursor 可以方便的在 XML tree 上行走。
要在程序中使用 XMLBeans, 程序需要包括 XBeans.jar. 可以从 XMLBeans 项目网站上下载。需要注意 license 的问题。清单 3 给出了用 XMLBeans API 实现的 Feed Query。
清单 3. 用 XMLBeans API 实现 Feed Query
package xml.xbean;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
public class XbeanFilterFeed implements main.IFilterFeed{
public OutputStream filter(InputStream is, String xpathExpr){
OutputStream os = System.out;
try {
// parse input stream into XML object
XmlObject input = XmlObject.Factory.parse(is);
//create output XML object
XmlObject output = XmlObject.Factory.newInstance();
//generate a copy version of input
output = input.copy();
//new XML cursor
XmlCursor outputCursor = output.newCursor();
//declare namespace using XML Object Xpath syntax
String expression = "declare namespace atom = 'http://www.w3.org/2005/Atom';" +
"declare namespace row = 'http://www.ibm.com/xmlns/atom/content/datarow/1.0';";
//append the xpath expression after namespace declaration
expression = expression + "atom:feed/atom:entry[not(" + xpathExpr + ")]";
// XML cursor performs xpath evaluation
outputCursor.selectPath(expression);
while(outputCursor.hasNextSelection()){
outputCursor.toNextSelection();
outputCursor.removeXml();
}
//dispose XML cursor
outputCursor.dispose();
//write XML object to output Stream
output.save(os);
} catch (XmlException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return os;
}
}
|
从程序清单 3 可以看出,用 XMLBeans API 实现的 Feed Query 的主要步骤如下:
-
XMLObject 静态方法获得 Factory,
-
解析输入的 XML 文档创建 XMLObject 对象
-
为创建的 XMLObject 对象创建 XMLCursor 对象用于遍历 XML。
-
用声明式的方式定义 namespace,并与 XPath expression 一块传给 XMLCursor。
-
执行 XPath evaluation。
-
序列化。XMLObject 的 save 方法很方便的实现 XMLObject 的序列化。
和 Abdera 一样,XMLBeans 提供了非常友好的 API,应用程序可以很方便的根据自己的需要进行 XML 的操作。除此之外,XMLBeans 还有一些别的优点:比如:DOM 需要在内存中生成整个文档的树。如果文档非常大,DOM 就会变得对内存非常敏感,并会显著降低性能。XMLBeans 通过增量解组(incremental unmarshalling)并提供 xget 方法来访问内置的模式数据类型,取得了较好的性能。 此外,XMLBeans 还能够访问完整的 XML Infoset,对于强调元素顺序或者注释的应用程序,这一点特别有用。XMLBeans 还提供了解析 XML 实例的即时验证。XMLBeans 包括一些创新的特性,如 XML 游标和对 XQuery 的支持。
XSLT 以及 XSLT API
XSLT 用于将一种 XML 文档转换为另外一种 XML 文档,或者可被浏览器识别的其他类型的文档,比如 HTML 和 XHTML。通常,XSLT 是通过把每个 XML 元素转换为 (X)HTML 元素来完成这项工作的。通过 XSLT,可以向或者从输出文件添加或移除元素和属性。也可重新排列元素,执行测试并决定隐藏或显示哪个元素,等等。
在转换过程中,XSLT 使用 XPath 来定义源文档中可匹配一个或多个预定义模板的部分。一旦匹配被找到,XSLT 就会把源文档的匹配部分转换为结果文档。下面来看下用 XSLT 技术来实现 Feed Query。
要使用 XSLT,首先需要创建 XSL,如清单 4 所示:
清单 4. 用于 Feed Query 的 XSL
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:row="http://www.ibm.com/xmlns/atom/content/datarow/1.0">
<xsl:template match="atom:feed">
<xsl:copy>
<!-- copy all nodes outside of "entry" -->
<xsl:copy-of select="node()[not(self::atom:entry)]"/>
<!-- processing entries -->
<xsl:for-each select="//atom:entry">
<!-- filtering -->
<xsl:if test="atom:content/row:row/row:Customer_Name = 'Ford'">
<xsl:copy>
<xsl:copy-of select="node()"/>
</xsl:copy>
</xsl:if>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
|
XSL 的语法在这里不多讲,不熟悉的读者请参见:XSLT 2.0 W3C Working Draft
接下来看看用 JAVA JDK XSLT API 的实现,见清单 5:
清单 5. 用 XSLT API 实现 Feed Query
package xml.xslt;
import java.io.InputStream;
import java.io.OutputStream;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class XSLTFilterFeed implements main.IFilterFeed{
public OutputStream filter(InputStream is, String xpathExpr){
OutputStream os = System.out;
try{
//create input xml stream source
StreamSource transrormSource = new StreamSource(is);
// create stylesheet stream source
InputStream xsl = XSLTFilterFeed.class.getResourceAsStream("filter.xsl");
Source styleSource = new StreamSource(xsl);
//new transformer
Transformer transformer = TransformerFactory.newInstance().newTransformer(styleSource);
//create output stream result
StreamResult transformedResult = new StreamResult(os);
//perform transformation
transformer.transform(transrormSource, transformedResult);
} catch(Exception ex){
ex.printStackTrace();
}
return os;
}
}
|
代码很简单。用 XLST API 的实现的主要难度是在写 XSL。对程序员的技能的要求比较高,需要熟悉 XSLT 和相应版本的 XPath 的语法。另外 Transform 可以 streaming 的进行,对于那些资源受限而又想获得较好的性能的程序比较适合。
Main 函数里面的调用
清单 6 列出了在 main 函数里面调用上述各方法实现的 Feed Query。
清单 6. main 函数调用
package main;
import java.io.InputStream;
public class Main {
public static void main(String[] args) {
String xpathExpr = "atom:content/row:row/row:Customer_Name = 'Ford'";
InputStream is = Main.class.getResourceAsStream("customerlist.xml");
//InputStream is = Main.class.getResourceAsStream("largeCustomeList.xml");
long starttime = 0;
long endtime = 0;
IFilterFeed filterfeed = null;
if(args[0].equalsIgnoreCase("dom")){
System.out.println("filtering feed using DOM API");
starttime = System.currentTimeMillis();
filterfeed = new xml.dom.DOMFilterFeed();
endtime = System.currentTimeMillis();
System.out.println("filtering feed using DOM API time-consuming: "
+ (endtime-starttime) + "ms" );
} else if(args[0].equalsIgnoreCase("abdera")){
System.out.println("filtering feed using abdera API");
starttime = System.currentTimeMillis();
filterfeed = new xml.Abdera.AbderaFilterFeed();
endtime = System.currentTimeMillis();
System.out.println("filtering feed using abdera API time-consuming: "
+ (endtime-starttime) + "ms" );
} else if(args[0].equalsIgnoreCase("xbean")){
System.out.println("filtering feed using xbean API");
starttime = System.currentTimeMillis();
filterfeed = new xml.xbean.XbeanFilterFeed();
endtime = System.currentTimeMillis();
System.out.println("filtering feed using xbean API time-consuming: "
+ (endtime-starttime) + "ms" );
} else if(args[0].equalsIgnoreCase("xslt")){
System.out.println("filtering feed using XSLT API");
starttime = System.currentTimeMillis();
filterfeed = new xml.xslt.XSLTFilterFeed();
endtime = System.currentTimeMillis();
System.out.println("filtering feed using XSLT API time-consuming: "
+ (endtime-starttime) + "ms" );
} else {
throw new RuntimeException("unsupported xml filter");
}
filterfeed.filter(is, xpathExpr);
}
}
|
可以在命令行运行或者在 IDE 里面运行 main 函数。如果是在 IDE 里面运行,请参照图 1 进行设置。Main 接受一个参数,可以指定使用哪种 API。通过运行 main,读者可以清楚的看到每种 API 的性能。读者可以自己做一些实验,比如输入不同大小的 feed,比较一下各种 API 对于不同文件大小的性能。
图 1. 在 IDE 里面设置 main 函数运行参数
下载 | 名字 | 大小 | 下载方法 |
|---|
| codesample | 3000K | HTTP |
注意: - Feed 的 API 的示例!
参考资料 学习
获得产品和技术
讨论
关于作者  | |  | 马春娥,工作在 IBM CSDL web2.0 team,开发人员,主要的关注点在web2.0领域的数据的建模,数据的处理,数据的可视化。 |
对本文的评价
|