本文是对我以前写过的这两篇文章的补充:
XML解析的三种方式
使用JAXB映射HashMap
在上面两篇文章中,我分别介绍了Java中常用的XML解析方式,以及如何使用JAXB实现XML和Java Class之间的映射。
在所有的XML解析方式当中,Sax和Stax比较适合在网络模块或者是处理很大的XML文件时使用,因为它们不是一次性将XML数据全部读入到内存,而是读取一部分,处理一部分,这样的方式非常高效。但是相比DOM而言,这种解析方式使得对代码变得很零碎,不能很好地用结构化的方式去处理XML。因此,如果我们可以用JAXB将Stream中的XML数据给映射成Java类,就可以同时结合Stax和JAXB的优势,实现XML的高效解析。
假设我们要处理的XML如下:
<?xml version="1.0" encoding="UTF-8"?>
<helloEntries>
<hello>
<name>world1</name>
</hello>
<hello>
<name>world2</name>
</hello>
<hello>
<name>world3</name>
</hello>
</helloEntries>
我们可以手写,也可以用《使用JAXB映射HashMap》中介绍的在线工具生成其对应的XSD:
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="helloEntries">
<xs:complexType>
<xs:sequence>
<xs:element ref="hello" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="hello">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="name"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
然后使用《使用JAXB映射HashMap》一文中介绍的xjc来生成对应的Java Class:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"hello"})
@XmlRootElement(name = "helloEntries")
public class HelloEntries {
protected List<Hello> hello;
public List<Hello> getHello() {
if (hello == null) {
hello = new ArrayList<Hello>();
}
return this.hello;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"name"})
@XmlRootElement(name = "hello")
public class Hello {
@XmlElement(required = true)
protected String name;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
}
接下来我们用Stax来读取XML,不同的是,在读取过程中,我们使用JAXB将每一个<hello>映射成Hello,进行数据的操作。因此,我们需要在START_ELEMENT这个EVENT当中进行JAXB绑定:
JAXBContext ctx = JAXBContext.newInstance(...);
Unmarshaller um = ctx.createUnmarshaller();
核心使用JAXB的Unmarshaller.unmarshall方法:
Object unmarshal(Reader reader)
throws JAXBException
注意到Unmarshaller中提供了几种不同的unmarshall函数,而我们在这里要使用的是输入参数为reader的方法,因为Stax正好是使用XMLStreamReader来提供流数据:
InputStream dataXml = ParseWithStax.class.getResourceAsStream("/helloEntries.xml");
XMLInputFactory inFactory = XMLInputFactory.newInstance();
XMLStreamReader r = inFactory.createXMLStreamReader(dataXml);
下面是完整的解析代码:
public class ParseWithStax {
public static void main(String[] args) throws Exception {
InputStream dataXml = ParseWithStax.class.getResourceAsStream("/helloEntries.xml");
XMLInputFactory inFactory = XMLInputFactory.newInstance();
XMLStreamReader r = inFactory.createXMLStreamReader(dataXml);
JAXBContext ctx = JAXBContext.newInstance("net.bluedash.snippets.jaxb");
Unmarshaller um = ctx.createUnmarshaller();
try {
int event = r.getEventType();
while (r.hasNext()) {
if (event == XMLStreamConstants.START_ELEMENT && r.getName().toString().equals("hello")) {
Hello hello = (Hello) um.unmarshal(r);
// process hello
}
event = r.next();
}
} finally {
r.close();
}
}
}
注意这里面的核心代码是:
if (event == XMLStreamConstants.START_ELEMENT && r.getName().toString().equals("hello")) {
Hello hello = (Hello) um.unmarshal(r);
// process hello
}
我们让Stax在流数据处理时遇到hello元素时,使用JAXB提供的Unmashaller进行数据绑定,注意JAXB会智能的只读取一个<hello>...</hello>。
我将这个例子放在了github上面,可以签出:
git clone git://github.com/liweinan/java-snippets.git
然后运行玩玩看:
mvn install
mvn -q exec:java -Dexec.mainClass="net.bluedash.snippets.jaxb.ParseWithStax"
参考资料
1.
A JAXB Tutorial
2.
Simple and efficient XML parsing using JAXB 2.0