Java XML 处理技术
目前 JAXP(Java API for XMLProcessiong)提供的处理 XML 文档的方法可以分为 2 种:DOM、SAX/STAX ,以及它们的拓展技术;
- DOM(Document Object MOdel)
DOM 文档对象模型是一种通过编程方式对 XML 文档中的数据模型及结构进行访问的标准;DOM 是基于 XML 文档在内存中的树状结构;优点:对于 XML 元素的操作性十分灵活;不足:当XML文件数据量很庞大时,内存开销会十分巨大;
- SAX(Simple API for XML)
SAX 允许开发者使用事件驱动的 XML 解析,和 DOM 不同,SAX 不要求将整个 XML 文件完全写入内存,而是当 XML 处理器完成对 XML 元素的操作时,立即调用一个自定义的事件处理器及时处理该元素和相关数据;优点:速度快、占用内存小;不足: 灵活性不如 DOM,如无法随机访问 XML 文档;
- STAX(Stream API for XML)
STAX 一种基于流的 XML 文档处理API,目的时为了解决 SAX 灵活性的问题,它可以在提高 XML 处理速度的同时,较好地兼顾灵活性;STAX 包括2套处理XML的API,分别应对不同程度的抽象:
基于指针的API:允许应用程序把 XML 作为一个标记(或事件)流来处理;基于迭代器的API:允许应用程序把 XML 作为一系列事件对象来处理;
DOM、SAX/STAX 技术都是从 XML 角度来处理文档和建立模型的,但是很多程序仅仅将 XML 作为一种数据交换,模型绑定手段,在这个层面上,目前有很多框架用于支持XML的模型绑定(无视底层实现),如:XStream、JAXB、JiBX、Castor 等;
XStream
XStream 是一套易用的开源类库,用于将 POJO 序列化为 XML / 将 XML 反序列化为 POJO,是 Java 对象和 XML 之间的一个双向转换器;
XStream 解析速度快、占用内存少,用户无需了解底层细节,允许指定转化器驱动(DOM,STAX等);
XStream 的架构包括以下组成部分:
- Converters(转化器)
当 XStream 需要转换对象时候,会委派给合适的转化器实现,默认提供的转化器包括:基本类型、String、Collections、Arrays、null、Date 等;
- I/O(输入输出)
XStream 通过 HierarchicalStramWriter、HierarchicalStramReader 接口分别进行序列化、反序列化;
- Context(上下文引用)
当 XStream 序列化、反序列化对象时,会分别创建类 MarshallingContext 、UnmarshallingContext ,由他们来处理数据并委派合适的转化器,XStream 提供了3种默认的上下文实现(可以通过XStream#setMode() 调整):XStream.XPATH_REFERENCES:(默认)通过 Xpath 来标识重复的引用;XStream.ID_REFERENCES:通过 ID 引用 来标识重复的引用XStream.NO_REFERENCES:对象作为树形结构,重复的引用被视为2个不同的对象,循环引用会导致异常,但是速度快、占用内存少;
- Facade(统一入口)
作为 XStream 的统一入口,将上面的重要组件集成在一起,以统一的接口开放;
XStream 不只可以作为 POJO 和 XML 之间的双向转化器,同时可以作为 POJO 和 JSON 之间的双向转化器;
使用 XStream 需要导入以下依赖:
com.thoughtworks.xstream:xstream(XStream的基础依赖)
org.codehaus.jettison:jettison(使用 XStream JSON 转换功能的依赖)
XStream 处理 XML
以下通过一个例子来演示 XStream 处理 XML 的基本使用过程;
POJO 类的标注(使用注解)
假设需要和 XML 进行相互转换的 POJO 如下:
User
import com.thoughtworks.xstream.annotations.*;
"user") (
public class User {
"id") (
private int userId;
"name") (
private String userName;
"city") (
private String userCity;
private String userDescription;
private List<LoginLog> logs;
//省略 getter and setter
}
其中 User 集合属性 logs 的元素对象 LoginLog
import com.thoughtworks.xstream.annotations.*;
"log") (
public class LoginLog {
"id") (
private int logId;
"ip") (
private String loginIp;
"date") (
DateConverter.class) (
private Date loginInDate;
//省略 getter and setter
}
可以看到这里使用 XStream 注解对 POJO 进行标注,XStream 常用的注解如下:
@XStreamAlias | 别名注解,作用于 类、字段 |
@XStreamAsAttribute | 转换为属性,作用于 字段 |
@XStreamOmitField | 忽略字段,作用于 字段 |
@XStreamConverter | 注入转化器,作用于 对象 |
@XStreamImplicit | 隐式集合,作用于 集合字段 |
转化器的编写
以上 LoginUser 对象的 loginInDate 字段为 Date 属性,如果由XStream默认转换,会直接调用 Date#toString 的方式输出该字段的值,我们有时候需要按照我们自己定义的格式输出一个 POJO 对象属性,可以使用转化器输出,对于 LoginUser.loginDate 属性,使用 “@XStreamConverter(DateConverter.class)” 标注其由 DateConverter 进行转换,以下是编写的转化器类:
DateConverter
import com.thoughtworks.xstream.converters.*;
import com.thoughtworks.xstream.io.*;
public class DateConverter implements Converter {
//判断需要转换的类型
public boolean canConvert(Class type) {
return Date.class.isAssignableFrom(type);
}
// POJO 到 XML 的转换逻辑
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
writer.setValue(new SimpleDateFormat("yyyy-MM-dd").format(source));
}
// XML 到 POJO 的转换逻辑
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Date dateObj = null;
try {
dateObj = new SimpleDateFormat("yyyy-MM-dd").parse(reader.getValue());
} catch (ParseException e) {
e.printStackTrace();
}
return dateObj;
}
}
使用 XStream
1)将 POJO 转换为 XML
//构造POJO对象
User user = .......;
//创建 XStream,使用 StaxDriver 驱动;
XStream xstream = new XStream(new StaxDriver());
xstream.processAnnotations(new Class[]{User.class, LoginLog.class}); //加载POJO类中的XStream注解
//xstream.autodetectAnnotations(true); //或者 自动加载POJO中的注解
//输出到内存
String xmlStr = xstream.toXML(user);
//输出到文件
xstream.toXML(user, new OutputStreamWriter(new FileOutputStream("./xstreamSample.xml"),"UTF-8"));
2)将 XML 转换为 POJO
//创建 XStream,使用 StaxDriver 驱动;
XStream xstream = new XStream(new StaxDriver());
xstream.processAnnotations(new Class[]{User.class, LoginLog.class});
//从内存获取XML字符串
User user1 = (User) xstream.fromXML(xmlStr);
//从文件获取XML字符串
User user2 = (User) xstream.fromXML(new InputStreamReader(new FileInputStream("./xstreamSample.xml"),"UTF-8"));
一个示例的 POJO 和 XML 的转化结果如下:
List<LoginLog> logs = new ArrayList<>();
logs.add(new LoginLog(1,"192.168.2.1",new Date()));
logs.add(new LoginLog(2,"192.172.156.2",new Date()));
logs.add(new LoginLog(3,"156.166.9.123",new Date()));
User user = new User();
user.setUserId(1);
user.setUserName("assad");
user.setUserCity("Guangzhou");
user.setLogs(logs);
<user id="1">
<name>assad</name>
<city>Guangzhou</city>
<log id="1">
<ip>192.168.2.1</ip>
<date>2018-01-13</date>
</log>
<log id="2">
<ip>192.172.156.2</ip>
<date>2018-01-13</date>
</log>
<log id="3">
<ip>156.166.9.123</ip>
<date>2018-01-13</date>
</log>
</user>
XStream 处理 JSON
XStream 除了可以处理 XML 以外,还可以处理 JSON ,而且使用和处理 XML 一样的 POJO 注解和转化器编写;
同样使用以上示例中的 User、LoginLog 和 DateConverter ,实现 POJO 和 JSON 之间的转换:
1)将 POJO 转换为 JSON
//构建 POJO 对象
User user = .....
//创建XStream,使用 JsonHierarchicalStreamDriver / JettisonMappedXmlDriver 驱动
XStream xstream = new XStream(new JsonHierarchicalStreamDriver());
xstream.processAnnotations(new Class[]{User.class, LoginLog.class});
//输出到内存
String jsonStr = xstream.toXML(user);
//输出到文件
xstream.toXML(user,new OutputStreamWriter(new FileOutputStream("./xStreamJsonSample.json"),"UTF-8"));
创建 XStream 时候可以使用 JsonHierarchicalStreamDriver 或 JettisonMappedXmlDriver 驱动,它们的区别如下:
JsonHierarchicalStreamDriver:输出格式良好的 JSON 串;
JettisonMappedXmlDriver:输出紧凑型格式的 JSON 串;
2)将 JSON 转换为 POJO
//创建XStream,使用 JettisonMappedXmlDriver / JsonHierarchicalStreamDriver 驱动
XStream xstream = new XStream(new JettisonMappedXmlDriver());
xstream.processAnnotations(new Class[]{User.class, LoginLog.class});
//从内存获取json字符串
User user1 = (User) xstream.fromXML(jsonStr);
//从文件获取json字符串
User user2 = (User) xstream.fromXML(new InputStreamReader(new FileInputStream("./xStreamJsonSample.json"),"UTF-8"));
一个示例的 POJO JSON 转化结果如下:
List<LoginLog> logs = new ArrayList<>();
logs.add(new LoginLog(1,"192.168.2.1",new Date()));
logs.add(new LoginLog(2,"192.172.156.2",new Date()));
logs.add(new LoginLog(3,"156.166.9.123",new Date()));
User user = new User();
user.setUserId(1);
user.setUserName("assad");
user.setUserCity("Guangzhou");
user.setLogs(logs);
{"user": {
"@id": "1",
"name": "assad",
"city": "Guangzhou",
"log": {
"@id": "1",
"ip": "192.168.2.1",
"date": "2018-01-13"
},
"log": {
"@id": "2",
"ip": "192.172.156.2",
"date": "2018-01-13"
},
"log": {
"@id": "3",
"ip": "156.166.9.123",
"date": "2018-01-13"
}
}}
流化处理对象
XStream 支持 java.io.ObjectInputStream 和 java.io.ObjectOutputStream 提供替代实现,允许以对象流的方式进行 XML 序列化和反序列化操作,对于处理集合对象非常有用,比如对于 List<User> users ,使用对象流的化,在内存中只会保存一个 User 对象流,这对于节约内存开销的帮助很大;
XStream 提供了 HierarchicalStreamWriter 用于流化对象处理 XML ,默认提供了 CompactWriter、PrettyPrintWriter 实现类支持;
1)流化 POJO 转换为 XML
User user = ....;
XStream xstream = new XStream(new StaxDriver());
xstream.processAnnotations(new Class[]{User.class, LoginLog.class});
PrettyPrintWriter ppw = new PrettyPrintWriter(new PrintWriter("./objectStreamSample.xml")); //格式化输出xml
//CompactWriter cw = new CompactWriter(new PrintWriter("./objectStreamSample.xml")); //紧凑型输出xml
//创建对象输出流,输出POJO到XML文件
ObjectOutputStream out = xstream.createObjectOutputStream(ppw);
out.writeObject(user);
out.close();
2)XML 转换为流化 POJO
XStream xstream = new XStream(new StaxDriver());
xstream.processAnnotations(new Class[]{User.class, LoginLog.class});
BufferedReader reader = new BufferedReader(new FileReader("./objectStreamSample.xml"));
//创建对象输入流,读取XML文件并转化为POJO
ObjectInputStream input = xstream.createObjectInputStream(reader);
User user = (User) input.readObject();
log.debug(user);