?
XML
技術是隨著
Java
的發展而發展起來的。在
XML
出現之前對于簡單的數據格式通常是存儲在
ini
配置文件等文本文件中,復雜的格式則采用自定義的文件格式,因此對于每種文件格式都要有專門的解析程序。
XML
出現以后解決了這個問題,程序面對的是有固定格式的
XML
文件,只要通過標準
API
就可以進行
XML
文件的處理。
XML
文件在案例系統中應用是很廣泛的,比如
ClientConfig.xml
、
ServerConfig.xml
文件就是使用
XML
文件來做配置文件的,元數據文件以及元數據加載器更是離不開
XML
。因此本章將系統講解一下
XML
文件的處理技術。
1.1
??
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
比
Dom4j
更加靈活和具有可擴展性;
Dom4j
的性能表現比
JDOM
好;
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
造成太大影響。
1.2
??
Dom4j的使用
Dom4j
是一個易用的、開源的庫,用于
XML
,
XPath
和
XSLT
。它應用于
Java
平臺,采用了
Java
集合框架并完全支持
DOM
,
SAX
和
JAXP
。
Dom4j
是
sourceforge.net
上的一個開源項目,地址為
http://sourceforge.net/projects/dom4j
。
Dom4j
里基于接口編程是一個非常顯著的優點,下面是其主要的接口的繼承體系結構圖:
圖
5
.
1
這些接口大部分都是定義在包
org.dom4j
中,下面簡單介紹各個接口的意義:
表
5
.
1
Dom4j
主要接口
Node
|
Node
為是
dom4j
中所有的
XML
節點的基類型接口
|
Attribute
|
Attribute
定義了
XML
的屬性
|
Branch
|
Branch
為能夠包含子節點的節點如
XML
元素
(Element)
和文檔
(Docuemnts)
定義了一個公共的行為
|
Document
|
定義了
XML
文檔
|
Element
|
Element
定義
XML
元素
|
DocumentType
|
DocumentType
定義
XML DOCTYPE
聲明
|
Entity
|
Entity
定義
XML entity
|
CharacterData
|
CharacterData
是一個標識借口,標識基于字符的節點。如
CDATA
,
Comment, Text
|
CDATA
|
CDATA
定義了
XML CDATA
區域
|
Comment
|
Comment
定義了
XML
注釋的行為
|
Text
|
Text
定義
XML
文本節點
|
ProcessingInstruction
|
ProcessingInstruction
定義
XML
處理指令
|
?
讀取
XML
文件
在
XML
應用中,最常用的莫過于
XML
文件的解析讀取了,
Dom4j
提供了多種讀取
XML
文檔的方式,包括
Dom
樹遍歷、
Visitor
方式和
XPath
方式。
無論哪種方式,我們首先都要根據
xml
文件構造一個
Document
對象:
SAXReader reader = new SAXReader();
Document document = reader.read(new File(fileName));
這里我們選用了
SAXReader
做為
XML
讀取器,我們同樣也可以選擇
DOMReader
做為
XML
讀取器:
SAXReader reader = new DOMReader();
Document document = reader.read(new File(fileName));
其中
reader
的
read
方法有多個重載方法,可以從
InputStream, File, URL
等多種不同的源來讀取
XML
文檔。
(
1
)
Dom
樹遍歷
這種讀取方式中把
Dom
看成一個普通的樹,要讀取
XML
中某個節點的值,只要采用數據結構中的樹遍歷算法定位到需要讀取的節點即可。
要便利
Dom
樹,首先要取得樹的根節點:
Element root = document.getRootElement();
取得根節點以后就可以一級一級的向下讀了:
//
遍歷所有子節點
for ( Iterator i = root.elementIterator(); i.hasNext(); )
{
??????
Element element = (Element) i.next();
??????
// do something
???
}
???
//
遍歷名稱為“
foo
”的節點
for ( Iterator i = root.elementIterator(
“
foo
”
); i.hasNext();)
{
??????
Element foo = (Element) i.next();
??????
// do something
???
}
???
//
遍歷屬性
for ( Iterator i = root.attributeIterator(); i.hasNext(); )
{
??????
Attribute attribute = (Attribute) i.next();
??????
// do something
}
(
2
)
Visitor
方式
Dom
樹遍歷是最普通,也是最常用的
XML
讀取方式,其他的
XML
解析引擎,比如
JDom
等,也是使用這種方式進行
XML
的讀取。不過
Dom4j
提供了另外一種讀取方式,那就是
Visitor
方式。這種方式實現了
Visitor
模式,調用者只要編寫一個
Visitor
就可以了。
Visitor
模式使得訪問者易于增加新的操作,同時使訪問者集中相關的操作而分離無關的操作。
編寫的
Visitor
必須實現
org.dom4j.Visitor
接口,
Dom4j
還提供了一個
Default Adapter
模式的默認適配器
org.dom4j.VisitorSupport
。
public class DemoVisitor extends VisitorSupport
{
public void visit(Element element)
{
System.out.println(element.getName());
}
public void visit(Attribute attr)
{
System.out.println(attr.getName());
}
}
然后在要開始遍歷的節點調用此
Visitor
即可:
root.accept(new DemoVisitor ())
此方式需要遍歷所有的節點和元素,因此速度會稍慢一些。
(
3
)
XPath
方式
Dom4j
最吸引人的特性莫過于對
XPath
的集成支持了,這個特性并不是所有的
XML
解析引擎都支持的,但是確實一個非常有用的特性。
XPath
是尋址、搜索和匹配文檔的各個部分的語言。它使用路徑標記法來指定和匹配文檔的各個部分,該標記法與文件系統和
URL
中使用的類似。例如,
XPath:/x/y/z
搜索文檔的根節點
x
,其下存在節點
y
,其下存在節點
z
。該語句返回與指定路徑結構匹配的所有節點。
/x/y/*
返回父節點為
x
的
y
節點下的任何節點。
/x/y[@name=a]
匹配所有父節點為
x
的
y
節點,其屬性稱為
name
,屬性值為
a
。
XPath
大大簡化了
XML
的尋址操作,使用者只要通過匹配表達式告訴引擎要匹配文檔的哪些部分即可,具體的匹配工作由
XPath
引擎來完成。這種方式更加接近于人類的自然思維方式。我們來看一個實際的例子:
有一個
XML
文件記錄了一個部門的基本情況:
<?xml version="1.0" encoding="GB2312"?>
<department>
???? <name>
開發部
</name>
???? <level>2</level>
???? <employeeList>
???????? <employee number="001" name="Tom" />
???????? <employee number="002" name="Jim" />
???????? <employee number="003" name="Lily" />
???? </employeeList>
</department>
name
代表部門名稱,
level
為部門的級別,
employeeList
下是部門所有的員工列表。下面編寫一個程序讀取此文件并打印出部門的信息。
代碼
5
.
1
XPath
演示
InputStream inStream = null;
try
{
???? inStream = Dom4jDemo01.class.getResourceAsStream(
"/com/cownew/Char0502/Department01.xml");
???? SAXReader reader = new SAXReader();
???? Document doc = reader.read(new InputStreamReader(inStream));
???? Node nameNode = doc.selectSingleNode("http://department/name");
???? System.out.println("
部門名稱
:" + nameNode.getText());
?
???? Node levelNode = doc.selectSingleNode("http://department/level");
???? System.out.println("
部門級別
:" + levelNode.getText());
?
???? List employeeNodeList = doc
?????????????????? .selectNodes("http://department/employeeList/employee");
???? System.out.println("
部門下屬雇員
:");
???? for (int i = 0, n = employeeNodeList.size(); i < n; i++)
???? {
???????? DefaultElement employeeElement = (DefaultElement) employeeNodeList
??????????????????????????? .get(i);
???????? String name = employeeElement.attributeValue("name");
???????? String number = employeeElement.attributeValue("number");
???????? System.out.println(name + "
,工號
:" + number);
???? }
} finally
{
???? ResourceUtils.close(inStream);
}
運行結果
:
部門名稱
:
開發部
部門級別
:2
部門下屬雇員
:
Tom
,工號
:001
Jim
,工號
:002
Lily
,工號
:003
?
使用
XPath
以后,我們只要使用“
//department/name
”這種非常清晰的方式就可以直接定位到具體的節點。
XPath
方式中定位單個節點使用
selectSingleNode
方法,而定位多節點則使用
selectNodes
方法。
案例系統中所有的
XML
文件都是使用
XPath
方式進行解析的,包括
ClientConfig.java
、
ServerConfig.java
、
EntityMetaDataParser.java
等。
XML
文件的創建
Dom4j
中
XML
文件的創建和其他的
XML
引擎類似,首先以
Document
的根節點為基礎構造出一棵節點樹,然后調用相應的
IO
類庫就可以將
XML
文件保存到適當的介質中了。
下面演示一下生成上文提到的那個部門信息
XML
文件的過程
:
代碼
5
.
2
XML
創建演示
import java.io.FileWriter;
import java.io.IOException;
?
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
?
public class Dom4jDemo02
{
???? public static void main(String[] args)
???? {
???????? //
創建文檔對象
???????? Document document = DocumentHelper.createDocument();
?
???????? //
添加根節點
"department"
???????? Element departElement = document.addElement("department");
?
???????? //
添加
"name"
節點
???????? Element departNameElement = DocumentHelper.createElement("name");
???????? departNameElement.setText("
開發部
");
???????? departElement.add(departNameElement);
?
???????? //
添加
"level"
節點
???????? Element departLevelElement = DocumentHelper.createElement("level");
???????? departLevelElement.setText("2");
???????? departElement.add(departLevelElement);
?
???????? //
添加員工列表
"employeeList"
節點
???????? Element employeeElementList = DocumentHelper
?????????????????? .createElement("employeeList");
???????? departElement.add(employeeElementList);
?
???????? //
添加員工節點
"employee"
???????? Element emp1Element = DocumentHelper.createElement("employee");
???????? emp1Element.addAttribute("number", "001");
???????? emp1Element.addAttribute("name", "Tom");
???????? employeeElementList.add(emp1Element);
?
???????? Element emp2Element = DocumentHelper.createElement("employee");
???????? emp2Element.addAttribute("number", "002");
???????? emp2Element.addAttribute("name", "Jim");
???????? employeeElementList.add(emp2Element);
?
???????? Element emp3Element = DocumentHelper.createElement("employee");
???????? //
添加屬性
???????? emp3Element.addAttribute("number", "003");
???????? emp3Element.addAttribute("name", "Lily");
???????? employeeElementList.add(emp3Element);
?
???????? try
???????? {
????????????? writeToFile(document, "c:/department.xml");
???????? } catch (IOException e)
???????? {
????????????? e.printStackTrace();
???????? }
???? }
?
???? private static void writeToFile(Document document, String file)
????????????? throws IOException
???? {
???????? //
美化格式
???????? OutputFormat format = OutputFormat.createPrettyPrint();
???????? format.setEncoding("GB2312");
???????? XMLWriter writer = null;
???????? try
???????? {
????????????? writer = new XMLWriter(new FileWriter(file), format);
????????????? writer.write(document);
???????? } finally
???????? {
????????????? if (writer != null)
?????????????????? writer.close();
???????? }
???? }
}
運行以后就可以在
c:/
下發現生成了和
5.2.1
的文件內容一樣的
department.xml
了。
這里有兩點需要注意的:
(
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
對象。
1.3
??
XStream的使用
在使用
XStream
之前首先到
http://xstream.codehaus.org
下載
XStream
的最新版本,然后把
XSteam***.jar
和
xpp3-***.jar
導入到
ClassPath
下,然后就可以使用了,當然不加入
xpp3-***.jar
也可以,我們可以使用
DomDriver
做為
XML
解析驅動(只要在實例化
XStream
的時候使用
new XStream(new DomDriver())
即可),不過
Xpp3
為
XStream
提供的一個很有效率的
XML pull-parser
實現,推薦使用,可以提高解析的效率。
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 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>
About Face
的作者
Tom
的
Email
為
:Tom@mail.com
UI Design
的作者
Jerry
的
Email
為
:Jerry@mail.com
?