XML 技术是随着 Java 的发展而发展起来的。在 XML 出现之前对于简单的数据格式通常是存储在 ini 配置文件等文本文件中,复杂的格式则采用自定义的文件格式,因此对于每种文件格式都要有专门的解析程序。 XML 出现以后解决了这个问题,程序面对的是有固定格式的 XML 文件,只要通过标准 API 就可以进行 XML 文件的处理。
XML 文件在案例系统中应用是很广泛的,比如 ClientConfig.xml 、 ServerConfig.xml 文件就是使用 XML 文件来做配置文件的,元数据文件以及元数据加载器更是离不开 XML 。因此总结一下处理技术。
XML处理技术比较
在 Java 领域 XML 文件的技术大致分为两类: XML API 和 OXMapping 。 XML API 是 XML 处理的基础,可选技术包括 JDOM 、 Dom4j 等; OXMapping 是 Object-XML Mapping 的简称,这种技术隐藏了 XML 底层操作的细节,可以将 XML 文件映射成一个 JavaBean 对象,也可以把一个 JavaBean 对象保存成一个 XML 文件,可选技术 XStream 、 Digester 、 Castor 等。 XML API 和 OXMapping 的关系类似于 JDBC 和 ORMaping 的关系, OXMapping 内部实现使用 XML API 来完成,两种实现技术从不同的层面实现了 XML 的处理。
XML API
此类 XML 处理技术中最流行的莫过于 JDOM 和 Dom4j 了,二者的使用方式非常相似。不过 Dom4j 的优势比 JDOM 更明显一些:
DOM4J是dom4j.org出品的一个开源XML解析包,它的网站中这样定义:
Dom4j是一个易用的、开源的库,用于XML,XPath和XSLT。它应用于Java平台,采用了Java集合框架并完全支持DOM,SAX和JAXP。
DOM4J使用起来非常简单。只要你了解基本的XML-DOM模型,就能使用。
dom4j是sourceforge.net上的一个开源项目,主要用于对XML的解析。从2001年7月发布第一版以来,已陆续推出多个版本。
dom4j专门针对Java开发,使用起来非常简单、直观,在Java界,dom4j正迅速普及。
可以到http://sourceforge.net/projects/dom4j下载其最新版。
dom4j1.5的完整版大约13M,是一个名为dom4j-1.5.zip的压缩包,解压后有一个dom4j-1.5.jar文件,这就是应用时需要引入的类包,另外还有一个jaxen-1.1-beta-4.jar文件,一般也需要引入,否则执行时可能抛java.lang.NoClassDefFoundError: org/jaxen/JaxenException异常,其他的包可以选择用之。
Dom4j 支持 XPath 等高级特性;
正是由于这些优点,很多开源项目都开始使用 Dom4j 做 XML 解析技术,本书也将使用 Dom4j 做为 XML 处理的首选。
OXMapping
使用 XML API 解析是略显烦琐的,受 ORMapping 技术的启发,人们发明了 OXMapping 技术,使用 OXMapping 技术,我们可以将 XML 文件映射成一个 JavaBean 对象,也可以把一个 JavaBean 对象保存成一个 XML 文件,这大大简化了我们的开发工作量,使得开发人员能更多的关注应用层面的东西。
开源世界中涌现出很多 OXMapping 框架,包括 XStream 、 Digester 、 Castor 等。 XStream 和 Digester 把映射的过程在代码中完成,而 Castor 则需要写一个和 Hibernate 中 cfg.xml 类似的映射配置文件。
与 Digester 比起来, XStream 的主要优点就是更加小巧,使用也更加方便,不过目前使用 Digester 是“开源名牌” Apache 下的子项目,网上可以参考的资料也比 XStream 多,好在 XStream 比较简洁,所以并不会对 XStream 造成太大影响。
============================================================================
一、利用dom4j解析xml
认识一下它的接口:
Attribute
Attribute定义了XML的属性
Branch
Branch为能够包含子节点的节点如XML元素(Element)和文档(Docuemnts)定义了一个公共的行为
CDATA
CDATA 定义了XML CDATA 区域
CharacterData
CharacterData是一个标识借口,标识基于字符的节点。如CDATA,Comment, Text.
Comment
Comment 定义了XML注释的行为
Document
定义了XML文档
DocumentType
DocumentType 定义XML DOCTYPE声明
Element
Element定义XML 元素
ElementHandler
ElementHandler定义了 Element 对象的处理器
ElementPath
被 ElementHandler 使用,用于取得当前正在处理的路径层次信息
Entity
Entity定义 XML entity
Node
Node为所有的dom4j中XML节点定义了多态行为
NodeFilter
NodeFilter 定义了在dom4j节点中产生的一个滤镜或谓词的行为(predicate)
ProcessingInstruction
ProcessingInstruction 定义 XML 处理指令.
Text
Text 定义XML 文本节点.
Visitor
Visitor 用于实现Visitor模式.
XPath
XPath 在分析一个字符串后会提供一个XPath 表达式
看名字大致就知道它们的涵义如何了。
================================================================
具体实现技术:
1. 读取并解析XML文档:
读写XML文档主要依赖于org.dom4j.io包,其中提供DOMReader和SAXReader两类不同方式,而调用方式是一样的。这就是依靠接口的好处。
从文件读取XML,输入文件名,返回XML文档
public Document read(String fileName) throws MalformedURLException, DocumentException {
SAXReader reader = new SAXReader();
Document document = reader.read(new File(fileName));
return document;
}
其中,reader的read方法是重载的,可以从InputStream, File, Url等多种不同的源来读取。得到的Document对象就带表了整个XML。
根据本人自己的经验,读取的字符编码是按照XML文件头定义的编码来转换。如果遇到乱码问题,注意要把各处的编码名称保持一致即可。
2. 取得Root节点
读取后的第二步,就是得到Root节点。熟悉XML的人都知道,一切XML分析都是从Root元素开始的。
public Element getRootElement(Document doc){
return doc.getRootElement();
}
3. 遍历XML树(暂时小结4种方法)
DOM4J提供至少3种遍历节点的方法:
1) 迭代所有子节点
for ( Iterator i = root.elementIterator(); i.hasNext(); ) {
Element element = (Element) i.next();
}
迭代名称为foo的节点,这招很牛可以准确定位指定类型的节点
for ( Iterator i = root.elementIterator(foo); i.hasNext();) {
Element foo = (Element) i.next();
}
迭代属性
for ( Iterator i = root.attributeIterator(); i.hasNext(); ) {
Attribute attribute = (Attribute) i.next();
}
2)递归
递归也可以采用Iterator作为枚举手段,但文档中提供了另外的做法
public void treeWalk() {
treeWalk(getRootElement());
}
public void treeWalk(Element element) {
for (int i = 0, size = element.nodeCount(); i < size; i++) {
Node node = element.node(i);
if (node instanceof Element) {
treeWalk((Element) node);
} else { // do something....
}
}
}
3) Visitor模式(访问者模式),很方变!
最令人兴奋的是DOM4J对Visitor的支持,这样可以大大缩减代码量,并且清楚易懂。了解设计模式的人都知道,Visitor是GOF设计模式之一。其主要原理就是两种类互相保有对方的引用,并且一种作为Visitor去访问许多Visitable。我们来看DOM4J中的Visitor模式(快速文档中没有提供)
只需要自定一个类实现Visitor接口即可。+
public class MyVisitor extends VisitorSupport {
public void visit(Element element){
System.out.println(element.getName());
}
public void visit(Attribute attr){
System.out.println(attr.getName());
}
}
调用: root.accept(new MyVisitor())
Visitor接口提供多种Visit()的重载,根据XML不同的对象,将采用不同的方式来访问。上面是给出的Element和Attribute的简单实现,一般比较常用的就是这两个。VisitorSupport是DOM4J提供的默认适配器,Visitor接口的Default Adapter模式,这个模式给出了各种visit(*)的空实现,以便简化代码。
注意,这个Visitor是自动遍历所有子节点的。如果是root.accept(MyVisitor),将遍历子节点。我第一次用的时候,认为是需要自己遍历,便在递归中调用Visitor,结果可想而知。
4). XPath支持,我比较喜欢用这个方法解析xml,目的性强,灵活!
DOM4J对XPath有良好的支持,如访问一个节点,可直接用XPath选择。
public void bar(Document document) {
注意:括号内路径的写法
List list = document.selectNodes( //foo/bar );
for(int i=0;i<list.size();i++){
Element ee=(Element)list.get(i);
String name=ee.getName();
String value=ee.getText();
}
Node node = document.selectSingleNode(//foo/bar/author);
//Element elem = (Element)doc.selectSingleNode("//subA");
String name = node.valueOf( @name );
}
例如,如果你想查找XHTML文档中所有的超链接,下面的代码可以实现:
public void findLinks(Document document) throws DocumentException {
List list = document.selectNodes( //a/@href );
for (Iterator iter = list.iterator(); iter.hasNext(); ) {
Attribute attribute = (Attribute) iter.next();
String url = attribute.getValue();
}
}
5. 字符串与XML的转换
有时候经常要用到字符串转换为XML或反之,
XML转字符串
Document document = ...;
String text = document.asXML();
字符串转XML
String text = “<person> <name>James</name> </person>“;
Document document = DocumentHelper.parseText(text);
6 、用XSLT转换XML
public Document styleDocument( Document document, String stylesheet ) throws Exception {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(new StreamSource( stylesheet )
);
// now lets style the given document
DocumentSource source = new DocumentSource( document );
DocumentResult result = new DocumentResult();
transformer.transform( source, result );
// return the transformed document
Document transformedDoc = result.getDocument();
return transformedDoc;
}
7. 创建XML
一般创建XML是写文件前的工作,这就像StringBuffer一样容易。
public Document createDocument() {
Document document = DocumentHelper.createDocument();
Element root = document.addElement(root);
Element author1 =
root
.addElement(author)
.addAttribute(name, James)
.addAttribute(location, UK)
.addText(James Strachan);
Element author2 =
root
.addElement(author)
.addAttribute(name, Bob)
.addAttribute(location, US)
.addText(Bob McWhirter);
return document;
}
8. 文件输出
一个简单的输出方法是将一个Document或任何的Node通过write方法输出
FileWriter out = new FileWriter( foo.xml );
document.write(out);
如果你想改变输出的格式,比如美化输出或缩减格式,可以用XMLWriter类
public void write(Document document) throws IOException {
XMLWriter writer = new XMLWriter(
new FileWriter( output.xml )
);
writer.write( document );
writer.close();
// 美化格式
OutputFormat format = OutputFormat.createPrettyPrint();
writer = new XMLWriter( System.out, format );
writer.write( document );
// 缩减格式
format = OutputFormat.createCompactFormat();
writer = new XMLWriter( System.out, format );
writer.write( document );
}
这里有两点需要注意的:
( 1 ) OutputFormat format = OutputFormat.createPrettyPrint()
XML 通常是需要人阅读的, Dom4j 默认的生成格式是紧缩格式的,这样可以减少空间占用,但是带来的缺点就是文件格式非常难看,因此我们采用锁紧格式进行输出。
( 2 ) format.setEncoding("GB2312")
Dom4j 默认的编码格式是“ UTF-8 ”,这在输出中文字符的时候会有问题,因此我们改成“ GB2312 ”格式。
这里使用了 Dom4j 提供的工具类 DocumentHelper 提供的 createElement 方法来创建一个节点,这个工具类还有 public static CDATA createCDATA(String text) 、 public static Comment createComment(String text) 、 public static Entity createEntity(String name, String text) 等方法可以帮助我们更快的创建节点。 DocumentHelper 还提供了 parseText 方法,可以直接将字符串解析成 Documen 对象。
9、修改节点、删除节点(略,写的太累拉)
Element有几个重要的方法:
addComment:添加注释
addAttribute:添加属性
addElement:添加子元素
addText:添加节点值
setText:修改节点值
setvalue:修改节点值
remove:删除节点值
===========================================================
二、利用xstream实现JAVABEAN和XML交互
在使用 XStream 之前首先到 http://xstream.codehaus.org 下载 XStream 的最新版本,然后把 XSteam***.jar 和 xpp3-***.jar 导入到 ClassPath 下,然后就可以使用了,当然不加入 xpp3-***.j
ar 也可以,我们可以使用 DomDriver 做为 XML 解析驱动(只要在实例化 XStream 的时候使用 new XStream(new DomDriver()) 即可),不过 Xpp3 为 XStream 提供的一个很有效率的 XML pull-parser 实现,推荐使用,可以提高解析的效率。
XStream是个很小的开源项目,所以它能实现的功能也就很有限,它只能包装简单类型的元素进行转换,复杂类型的,比如Calendar,Date等类型则需要用户自己实现Converter。
还有,XStream对xml的操作并不好,比如想把一个子节点变成属性就是一个很复杂的工作,但是大家平时用于测试的话,看看对象中到底存着什么数据,还是必要的。
XML 的解析
我们有一个记录书籍进行的 XML 文件:
<book>
<name>J2EE Guide Book</name>
<author>
<name>Jerry</name>
<email>Jerry@mail.com</email>
</author>
</book>
为了解析此 XML 文件,我们首先创建代表书籍和人员的两个 JavaBean 。
代码 5 . 3 人员和书籍的 JavaBean
public class BookInfo
{
private String name;
private PersonInfo author;
public PersonInfo getAuthor()
{
return author;
}
public void setAuthor(PersonInfo author)
{
this.author = author;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
public class PersonInfo
{
private String name;
private String email;
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
然后我们就可以进行文件的解析了,这也是重头戏:
代码 5 . 4 XStream 的 XML 解析
XStream xstream = new XStream();
xstream.alias("book", BookInfo.class);
xstream.alias("author", PersonInfo.class);
InputStream inStream = XStreamDemo.class
.getResourceAsStream("/com/cownew/Char0503/Books.xml");
InputStreamReader reader = new InputStreamReader(inStream);
BookInfo book = (BookInfo) xstream.fromXML(reader);
StringBuffer sb = new StringBuffer();
sb.append(book.getName()).append(" 的作者 ");
sb.append(book.getAuthor().getName()).append(" 的 Email 为 :");
sb.append(book.getAuthor().getEmail());
System.out.println(sb);
运行结果:
J2EE Guide Book 的作者 Jerry 的 Email 为 :Jerry@mail.com
由于 book 节点和 author 节点对应的数据类型是我们的自定义类型,因此我们必须首先向 XStream 注册这两个类型 :
xstream.alias("book", BookInfo.class);
xstream.alias("author", PersonInfo.class);
由于我们是使用 XStream 解析已有的 XML 文件,因此我们必须让 XStream 知道标签对应的类型是什么,如果我们是使用 XStream 进行 XML 文件的生成,那么我们甚至无需向 XStream 注册别名即可进行文件解析。
注册完类型以后,调用 XStream 类的 fromXML 方法即可把 XML 解析成 JavaBean 对象,无需额外的操作。
XML 文件的保存
我们不仅需要解析 XML 文件,有的时候还需要将数据保存到 XML 文件, XStream 同样能很好的完成,并且能更体现出 XStream 的强大。
代码 5 . 5 XStream 中 XML 的保存
List bookList = new ArrayList();
PersonInfo p1 = new PersonInfo();
p1.setName("Tom");
p1.setEmail("Tom@mail.com");
PersonInfo p2 = new PersonInfo();
p2.setName("Jerry");
p2.setEmail("Jerry@mail.com");
BookInfo book1 = new BookInfo();
book1.setName("About Face");
book1.setAuthor(p1);
BookInfo book2 = new BookInfo();
book2.setName("UI Design");
book2.setAuthor(p2);
bookList.add(book1);
bookList.add(book2);
XStream xstream = new XStream();
String xml = xstream.toXML(bookList);
System.out.println(xml);
List list = (List) xstream.fromXML(xml);
for(int i=0,n=list.size();i<n;i++)
{
BookInfo book = (BookInfo) list.get(i);
StringBuffer sb = new StringBuffer();
sb.append(book.getName()).append(" 的作者 ");
sb.append(book.getAuthor().getName()).append(" 的 Email 为 :");
sb.append(book.getAuthor().getEmail());
System.out.println(sb);
}
运行结果:
<list>
<com.cownew.Char0503.BookInfo>
<name>About Face</name>
<author>
<name>Tom</name>
<email>Tom@mail.com</email>
</author>
</com.cownew.Char0503.BookInfo>
<com.cownew.Char0503.BookInfo>
<name>UI Design</name>
<author>
<name>Jerry</name>
<email>Jerry@mail.com</email>
</author>
</com.cownew.Char0503.BookInfo>
</list>
About Face 的作者 Tom 的 Email 为 :Tom@mail.com
UI Design 的作者 Jerry 的 Email 为 :Jerry@mail.com
不可思议吧!我们就是像在序列化一样把 JavaBean “序列化”为 XML 格式字符串,然后又轻松的将 XML 格式字符串“反序列化”为 JavaBean 。
不过美中不足的就是“ <com.cownew.Char0503.BookInfo> ”这个标签显得有点罗嗦。解决方式很简单,使用 5.3.1 一节中提到的 alias 方法就可以办到:
将 xstream.alias("book", BookInfo.class);如:xstream.alias("book",com.nownew.Char0503.BookInfo.class) 添加到 XStream xstream = new XStream(); 之后,然后重新运行:
<list>
<book>
<name>About Face</name>
<author>
<name>Tom</name>
<email>Tom@mail.com</email>
</author>
</book>
<book>
<name>UI Design</name>
<author>
<name>Jerry</name>
<email>Jerry@mail.com</email>
</author>
</book>
</list>