Xml 現在仍然占據着比較重要的地位,比如微信接口中使用了 Xml 進行消息的定義。本章重點討論 Xml 的新建、編輯、查找、轉化,可以這么理解,本章是使用了 dom4j、xstream 也是在開發者中使用最為廣泛的 。 本章主要是位大家提供一個操作 Xml 的類庫。
0 Dom4j XStream 簡單介紹
一句話 Dom4j 專注於 Xml 操作的高性能庫,Xstream 則專注於 對象之間的轉換。
Dom4j
Dom4j 為了支持 XPath、XML Schema、基於事件處理大文檔或流文檔。
Dom4j 為提供構建文檔表示的選項,為可通過 Dom4j-API 和標准底層 dom-API 並行訪問功能。
為實現上述宏偉目標,Dom4j 使用接口和抽象基本類方法並大量使用 JDK 中 Collections 類。
所以 Dom4j 有豐富的 API,在靈活性上面 Dom4j 更占有優勢,性能方面也無可挑剔。
聲名在外的 Sun-JAXM,大名鼎鼎的 Hibernate 中XML 配置文件解析都使用的是 Dom4j。
XStream
XStream 為基於注解不需要其它輔助類或映射文件 的OXMapping 技術,如果你用過 hibernate 或 mybatis 之類的 ORM 框架就不難理解這里的 OXM。
XStream 可以將 JavaBean 序列化為 XML,或將 XML 反序列化為 JavaBean,使得XML序列化不再繁瑣。
XStream 也可以將 JavaBean 序列化成 Json 或反序列化,使用非常方便。
沒有映射文件而且底層使用 xmlpull 推模型解析 XML,高性能、低內存占用,結合簡潔明了的 API,上手基本是分分鍾的事情。
XStream 同時也可以定制轉換類型策略並配有詳細的錯誤診斷,能讓你快速定位問題。
1 新建 Spring Boot Maven 示例工程項目
注意:是用來 IDEA 開發工具
- File > New > Project,如下圖選擇
Spring Initializr
然后點擊 【Next】下一步 - 填寫
GroupId
(包名)、Artifact
(項目名) 即可。點擊 下一步
groupId=com.fishpro
artifactId=xmldom4j - 選擇依賴
Spring Web Starter
前面打鈎。 - 項目名設置為
spring-boot-study-xmldom4j
.
2 引入依賴 Pom
- dom4j-1.6.1 支持 Java 1.4+
- dom4j-2.0.2 支持 Java 5+
- dom4j-2.1.1 支持 Java 8+
mvnrepository 只有 1.6.1 那就用1.6.1
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/jaxen/jaxen xpath解析的時候需要引入 jaxen包 否則會報錯 java.lang.NoClassDefFoundError: org/jaxen/JaxenException-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream 支持xml轉bean -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.11.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3 Dom4j 代碼實例
3.1 打開一個遠程 xml
/**
* 解析遠程 XML 文件
* @return Document xml 文檔
* */
public static Document parse(URL url) throws DocumentException{
SAXReader reader = new SAXReader();
Document document =reader.read(url);
return document;
}
3.2 創建一個 xml 文檔
/**
* 創建一個 xml 文檔
* */
public static 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;
}
3.3 遍歷
System.out.println("====遍歷================================");
//獲取根節點
Element root = document.getRootElement();
// 遍歷根節點下的子節點
for (Iterator<Element> it = root.elementIterator(); it.hasNext();) {
Element element = it.next();
// do something
System.out.println("根節下子節點名稱:"+element.getName());
}
// 遍歷子節點 author 下的子節點
for (Iterator<Element> it = root.elementIterator("feed"); it.hasNext();) {
Element foo = it.next();
// do something
System.out.println("author節點下節點名稱:"+foo.getName());
}
// 后去根節點的屬性
for (Iterator<Attribute> it = root.attributeIterator(); it.hasNext();) {
Attribute attribute = it.next();
// do something
System.out.println("根節下子節點屬性:"+attribute.getName());
}
3.4 使用 xpath 獲取節點
System.out.println("================================================");
//使用 XPath 獲取節點 獲取多個節點
List<Node> list = document.selectNodes("//feed/entry");
for (Node node :list
) {
System.out.println("list node:"+node.getName());
}
Node node = document.selectSingleNode("//feed");
System.out.println("node:"+node.getName());
3.5 保存到 文件
//寫到文件里面
Document document1=Dom4jUtils.createDocument();
FileWriter out = new FileWriter("foo.xml");
document1.write(out);
out.close();
3.6 XML 文件轉文本
Document xd=DocumentHelper.parseText(text);
3.7 文本轉 XML 文檔對象
String text = "<person> <name>James</name> </person>";
Document document = DocumentHelper.parseText(text);
3.8 使用 XSLT 轉換 XML
/**
* 使用XSLT轉換XML
* */
public static Document styleDocument(Document document, String stylesheet) throws Exception {
TransformerFactory factory = TransformerFactory.newInstance();//實例化轉換器工廠 TransformerFactory
Transformer transformer = factory.newTransformer(new StreamSource(stylesheet));// 創建一個轉化格式對象
DocumentSource source = new DocumentSource(document); // XML 源對象
DocumentResult result = new DocumentResult(); //轉換結果對象
transformer.transform(source, result);//轉換操作
Document transformedDoc = result.getDocument();//獲取轉換后的文檔
return transformedDoc;
}
4 XStream 代碼實例
4.1 不使用別名直接輸出 xml
Blog.java (路徑 src/main/java/com/fishpro/xmldom4j/domain/Blog.java)
public class Blog {
private Author writer;
private List entries = new ArrayList();
public Blog(Author writer) {
this.writer = writer;
}
public void add(Entry entry) {
entries.add(entry);
}
public List getContent() {
return entries;
}
}
部分代碼
Blog teamBlog = new Blog(new Author("Guilherme Silveira"));
teamBlog.add(new Entry("first","My first blog entry."));
teamBlog.add(new Entry("tutorial",
"Today we have developed a nice alias tutorial. Tell your friends! NOW!"));
//1.如果不設置別名呢
System.out.println(xstream.toXML(teamBlog));
/** 1.不設置別名輸出
* <com.fishpro.xmldom4j.domain.Blog>
* <writer>
* <name>Guilherme Silveira</name>
* </writer>
* <entries>
* <com.fishpro.xmldom4j.domain.Entry>
* <title>first</title>
* <description>My first blog entry.</description>
* </com.fishpro.xmldom4j.domain.Entry>
* <com.fishpro.xmldom4j.domain.Entry>
* <title>tutorial</title>
* <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
* </com.fishpro.xmldom4j.domain.Entry>
* </entries>
* </com.fishpro.xmldom4j.domain.Blog>
* */
4.2 使用別名直接輸出 xml
注意使用了上面代碼的 Blog teamBlog 的定義。注釋部分為輸出效果
//2.設置別名
xstream.alias("blog", Blog.class);
xstream.alias("entry", Entry.class);
System.out.println(xstream.toXML(teamBlog));
/** 2.設置別名
* <blog>
* <writer>
* <name>Guilherme Silveira</name>
* </writer>
* <entries>
* <entry>
* <title>first</title>
* <description>My first blog entry.</description>
* </entry>
* <entry>
* <title>tutorial</title>
* <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
* </entry>
* </entries>
* </blog>
* */
4.3 替換節點名稱
注意使用了上面代碼的 Blog teamBlog 的定義。注釋部分為輸出效果
//3.writer 節點 轉為 author節點
System.out.println("2.writer 節點 轉為 author節點");
xstream.useAttributeFor(Blog.class, "writer");
xstream.aliasField("author", Blog.class, "writer");
System.out.println(xstream.toXML(teamBlog));
xstream.addImplicitCollection(Blog.class, "entries");
System.out.println(xstream.toXML(teamBlog));
/** 3. author 替代了 write
* <blog>
* <author>
* <name>Guilherme Silveira</name>
* </author>
* <entry>
* <title>first</title>
* <description>My first blog entry.</description>
* </entry>
* <entry>
* <title>tutorial</title>
* <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
* </entry>
* </blog>
* */
4.4 實體對象屬性作為節點的屬性
注意使用了上面代碼的 Blog teamBlog 的定義。注釋部分為輸出效果
//4.writer 作為 blog 的屬性
System.out.println("4.作為blog的屬性");
xstream.useAttributeFor(Blog.class, "writer");
xstream.registerConverter(new AuthorConverter());//作為blog的屬性
System.out.println(xstream.toXML(teamBlog));
/** 4.writer 作為 blog 的屬性
* <blog author="Guilherme Silveira">
* <entry>
* <title>first</title>
* <description>My first blog entry.</description>
* </entry>
* <entry>
* <title>tutorial</title>
* <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
* </entry>
* </blog>
* */
4.5 使用注解 @XStreamAlias
@XStreamAlias 可以應用到類似,也可以應用到實體對象 Bean 的屬性名上
本示例中使用了 User 和 Address 實體,他們的關系是 User 可以擁有多個 Address
User (路徑 src/main/java/com/fishpro/xmldom4j/domain/User.java)
@XStreamAlias("user")
public class User {
@XStreamAlias("id")
private Integer userId;
@XStreamAlias("username")
private String username;
@XStreamImplicit
private List<Address> addresses;
public User(Integer userId,String username){
this.userId=userId;
this.username=username;
}
public User(Integer userId,String username,List<Address> addresses){
this.userId=userId;
this.username=username;
this.addresses=addresses;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
}
Address (路徑 src/main/java/com/fishpro/xmldom4j/domain/Address.java)
@XStreamAlias("address")
public class Address {
private String street;
private String zipcode;
private String mobile;
public Address(String street,String zipcode,String mobile){
this.street=street;
this.zipcode=zipcode;
this.mobile=mobile;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
}
System.out.println("5.使用注解");
//5.使用注解
xstream.processAnnotations(User.class);
User msg = new User(1, "fishpro");
System.out.println(xstream.toXML(msg));
/** 使用注解 @XStreamAlias("user")
* <user>
* <id>1</id>
* <username>fishpro</username>
* </user>
* */
4.6 使用注解 @XStreamImplicit
注意使用了上面代碼的 Blog teamBlog 的定義。注釋部分為輸出效果
//6.使用注解 子節點是列表
List<Address> addressList=new ArrayList<>();
addressList.add(new Address("江蘇省南京市玄武大道1000號","201001","1801989098"));
addressList.add(new Address("江蘇省南京市玄武大道1001號","201001","1811989098"));
msg = new User(1, "fishpro",addressList);
System.out.println(xstream.toXML(msg));
/** 6.使用注解 子節點是列表
* <user>
* <id>1</id>
* <username>fishpro</username>
* <address>
* <street>江蘇省南京市玄武大道1000號</street>
* <zipcode>201001</zipcode>
* <mobile>1801989098</mobile>
* </address>
* <address>
* <street>江蘇省南京市玄武大道1001號</street>
* <zipcode>201001</zipcode>
* <mobile>1811989098</mobile>
* </address>
* </user>
* */
4.9 屬性轉換器
當我們遇到日期的是,可能需要轉換成想要的格式,我們給 User 對象增加 created 屬性
private Calendar created = new GregorianCalendar();
那么上面的示例就會輸出,注意以下多出了created節點
<user>
<id>1</id>
<username>fishpro</username>
<address>
<street>江蘇省南京市玄武大道1000號</street>
<zipcode>201001</zipcode>
<mobile>1801989098</mobile>
</address>
<address>
<street>江蘇省南京市玄武大道1001號</street>
<zipcode>201001</zipcode>
<mobile>1811989098</mobile>
</address>
<created>
<time>1565691626712</time>
<timezone>Asia/Shanghai</timezone>
</created>
</user>
接下來我們新建一個轉換器類
SingleValueCalendarConverter (路徑 src/main/java/com/fishpro/xmldom4j/util/SingleValueCalendarConverter.java)
/**
* 日期轉換器
* */
public class SingleValueCalendarConverter implements Converter {
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
Calendar calendar = (Calendar) source;
writer.setValue(String.valueOf(calendar.getTime().getTime()));
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(new Date(Long.parseLong(reader.getValue())));
return calendar;
}
public boolean canConvert(Class type) {
return type.equals(GregorianCalendar.class);
}
}
在運行 main 示例,如下 created 節點變化了
<user>
<id>1</id>
<username>fishpro</username>
<address>
<street>江蘇省南京市玄武大道1000號</street>
<zipcode>201001</zipcode>
<mobile>1801989098</mobile>
</address>
<address>
<street>江蘇省南京市玄武大道1001號</street>
<zipcode>201001</zipcode>
<mobile>1811989098</mobile>
</address>
<created>1565691762404</created>
</user>
4.10 反序列化
XStream xstream = new XStream();
xstream.alias("person", Person.class);//設置節點person的名稱
xstream.alias("phonenumber", PhoneNumber.class);
Person joe = new Person("Joe", "Walnes");
joe.setPhone(new PhoneNumber(123, "1234-456"));
joe.setFax(new PhoneNumber(123, "9999-999"));
String xml = xstream.toXML(joe);//對象轉 xml
System.out.println(xml);
/** 輸出簡單示例xml
* <person>
* <firstname>Joe</firstname>
* <lastname>Walnes</lastname>
* <phone>
* <code>123</code>
* <number>1234-456</number>
* </phone>
* <fax>
* <code>123</code>
* <number>9999-999</number>
* </fax>
* </person>
* */
Person newJoe = (Person)xstream.fromXML(xml);//xml 轉 對象
問題:
Exception in thread "main" java.lang.NoClassDefFoundError: org/jaxen/JaxenException
需要引入
<!-- https://mvnrepository.com/artifact/jaxen/jaxen -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
參考