XML处理利器:XStream
-
XStream 概述
XStream是一套简洁易用的开源类库,用于将Java对象序列化为XML或者将XML反序列化为Java对象,是Java对象和XML之间的一个双向转化器。
XStream主要特点
1 灵活易用:提供了简单,灵活,易用的统一接口,用户无需了解底层细节
2 无需映射:大多数对象都可以在无需映射的情况下进行序列化和反序列化的操作。
3 高速稳定:解析速度快,占用内存少。
4 灵活转换:转换策略是可以定制的,允许自定义类型存储为指定的XML格式。
5 易于集成:通过实现特定的接口,可以直接与其他任何树状结构进行序列化与反序列化操作。
-
XStream 架构组成
1 Convert(转换器)
当 XStream遇到需要转换的对象时,它会委派给合适的转换器实现。XStream为通用类型提供了多种转换器实现,包括基本数据类型,String,Collections,Arrays,null,Date等。
2 I/O(输入/输出)
XStream是通过接口HierarchicalStreamWriter和HierarchicalStreamReader从底层XML数据中抽象而来的,分别用于序列化和反序列化操作。
3 Context(上下文引用)
在XStream序列化和反序列化对象时,它会创建两个类 MarshallingContext和UnmarshallingContext,由他们来处理数据并委派合适的转换器。XStream提供了三种上下文的默认实现,它们之间存在细微的差别。默认值可以通过XStream.setMode()方法调整,可选值如下:
(1)XStream.XPATH_REFERENCES:(默认值),通过XPath引用来标识重复的引用。
(2)XStream_ID_REFERENCES:使用Id引用来标识重复引用
(3)XStream_NO_REFERENCES:对象作为树形结构,重复的引用被视为两个不同的对象,循环引用会导致异常发生。这种模式速度快,占用内存少。
4 Facade(统一入口)
作为XStream的统一入口点,它将上年所提及的重要组件集成在一起,以统一的接口开放出来。
-
XStream使用
引入一下jar包
<dependency> <groupId>com.thougthworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.9</version> </dependency>
使用代码如下:
public class XstreamSample { private static XStream xStream; static { xStream = new XStream(new DomDriver()); } public static User getUser() { LoginLog loginLog = new LoginLog(); loginLog.setIp("192.168.1.91"); loginLog.setLoginLogId(1); loginLog.setLoginDate(new Date()); User user = new User(); user.setUserName("wsq"); user.setUserId(1); user.setLogs(Lists.newArrayList(loginLog)); return user; } public static void main(String args[]) { try { objectToXml(); xmlToObject(); } catch (Exception e) { System.out.println("转换异常" + e); } } public static void objectToXml() throws Exception { User user = getUser(); FileOutputStream outputStream = new FileOutputStream("/Users/wsq/Desktop/XstreamSample.xml"); xStream.toXML(user, outputStream); } public static void xmlToObject() throws Exception { FileInputStream outputStream = new FileInputStream("/Users/wsq/Desktop/XstreamSample.xml"); User user = (User) xStream.fromXML(outputStream); for (LoginLog login : user.getLogs()) { System.out.println(login); } } @Data public static class User { private int userId; private String userName; private String password; private int credits; private String lastIp; private Date lastVisit; private List<LoginLog> logs; } @Data public static class LoginLog { private int loginLogId; private int userId; private String ip; private Date loginDate; } }
生成的XML文件
<cn.com.servyou.xqy.gaia.facadeimpl.XstreamSample_-User> <userId>1</userId> <userName>wsq</userName> <credits>0</credits> <logs> <cn.com.servyou.xqy.gaia.facadeimpl.XstreamSample_-LoginLog> <loginLogId>1</loginLogId> <userId>0</userId> <ip>192.168.1.91</ip> <loginDate>2020-06-14 06:50:46.642 UTC</loginDate> </cn.com.servyou.xqy.gaia.facadeimpl.XstreamSample_-LoginLog> </logs> </cn.com.servyou.xqy.gaia.facadeimpl.XstreamSample_-User>
使用XStream别名
在默认情况下,Java对象到XML的映射是Java对象属性名对应XML的元素名,Java类的全名对应XML根元素的名字。在实际应用中,如果XML和Java类都已经存在相应的名字,那么,在进行转换时,需要设置别名进行映射。
XStream别名配置包含三种情况
- 类别名:用alias(String name,Class type).
- 类成员别名:用aliasField(String alias,Class definedIn,String fieldName).
- 类成员作为属性别名:用aliasAttribute(Class definedIn,String attributeName,String alias).单独命名没有意义,还要通过userAttributeFor(Class definedIn,String fieldName)应用到某个类上。
从上一个实例生成的XML文件来看,生成的XML 元素结构不是很友好。接下来使用XStream提供的别名机制来修饰生成的XML元素的结构。如下所示:
修改之前的代码为:
static { xStream = new XStream(new DomDriver()); xStream.alias("loginLog", LoginLog.class); xStream.alias("user", User.class); xStream.aliasField("id", User.class, "userId"); xStream.aliasAttribute(LoginLog.class, "userId", "id"); xStream.useAttributeFor(LoginLog.class, "userId"); xStream.addImplicitCollection(User.class, "logs"); }
生成XML如下:
<user> <id>1</id> <userName>wsq</userName> <credits>0</credits> <loginLog id="0"> <loginLogId>1</loginLogId> <ip>192.168.1.91</ip> <loginDate>2020-06-14 07:18:16.818 UTC</loginDate> </loginLog> </user>
-
XStream转换器
在开发过程中,有时可能需要转换一些自定义类型,此时默认的映射方式可能无法满足需要。不用担心,XStream已经提供了丰富的扩展点,用户可以实现自己的转换器,然后调用registerConvert()方法注册自定义的转换器。实现自定义的转换器很简单,只要实现XStream提供的Convert接口并实现其方法即可。
上一个实例已经成功运用XStream进行对象与XML的相互转换,并应用XStream提供的别名机制设置生成XML的结构。下面使用XStream提供的转换器扩展接口,对生成的XML文件继续优化。
首先需要实现Convert接口来编写一个日期转换器,具体代码如下:
d
public class DateConvert implements Converter { private Locale locale; public DateConvert(Locale locale) { super(); this.locale = locale; } //编写Java对象到 XML的转换逻辑 @Override public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { DateFormat formatter = DateFormat.getDateInstance(DateFormat.DATE_FIELD, this.locale); writer.setValue(formatter.format(value)); } //编写 XML 到 java的转换逻辑 @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { GregorianCalendar calendar = new GregorianCalendar(); DateFormat formatter = DateFormat.getDateInstance(DateFormat.DATE_FIELD, this.locale); try { calendar.setTime(formatter.parse(reader.getValue())); } catch (Exception e) { throw new ConversionException(e.getMessage(), e); } return calendar.getGregorianChange(); } //判断要转换的类型 @Override public boolean canConvert(Class aClass) { return Date.class.isAssignableFrom(aClass); } }
剩下的事情就是调用XStream#registerConvert()方法注册自定义的日期转换器。
xStream.registerConverter(new DateConvert(Locale.forLanguageTag("CN")));
XML内容如下:
<user> <id>1</id> <userName>wsq</userName> <credits>0</credits> <loginLog id="0"> <loginLogId>1</loginLogId> <ip>192.168.1.91</ip> <loginDate>6/14/20</loginDate> </loginLog> </user>
-
XStream注解
注解 说明 作用目标 @XStreamAlias 别名注解 类,字段 @XStreamAsAttribute 转换成属性 字段 @XStreamOmitField 忽略字段 字段 @XStreamConvert 注入转换器 对象 @XStreamImplicit 隐式集合 集合字段 XStream不但可以通过编码的方式对XML进行转换,而且支持基于注解的方式进行转换。下面使用XStream提供的注解来改造上面的实例。首先需要对User和LoginLog添加相应的注解
如下所示:
public class XstreamSample { private static XStream xStream; static { xStream = new XStream(new DomDriver()); //实现该方法,判断要转换的类型 xStream.processAnnotations(User.class); xStream.processAnnotations(LoginLog.class); //自动加载注解Bean xStream.autodetectAnnotations(true); } public static User getUser() { LoginLog loginLog = new LoginLog(); loginLog.setIp("192.168.1.91"); loginLog.setLoginLogId(1); loginLog.setLoginDate(new Date()); User user = new User(); user.setUserName("wsq"); user.setUserId(1); user.setLogs(Lists.newArrayList(loginLog)); return user; } public static void main(String args[]) { try { objectToXml(); xmlToObject(); } catch (Exception e) { System.out.println("转换异常" + e); } } public static void objectToXml() throws Exception { User user = getUser(); FileOutputStream outputStream = new FileOutputStream("/Users/wsq/Desktop/XstreamSample.xml"); xStream.toXML(user, outputStream); } public static void xmlToObject() throws Exception { FileInputStream outputStream = new FileInputStream("/Users/wsq/Desktop/XstreamSample.xml"); User user = (User) xStream.fromXML(outputStream); for (LoginLog login : user.getLogs()) { System.out.println(login); } } @Data @XStreamAlias("user") public static class User { @XStreamAsAttribute @XStreamAlias("id") private int userId; @XStreamAlias("userName") private String userName; @XStreamAlias("password") private String password; @XStreamAlias("credits") private int credits; @XStreamAlias("lastIp") private String lastIp; @XStreamConverter(DateConvert.class) private Date lastVisit; @XStreamImplicit private List<LoginLog> logs; } @Data public static class LoginLog { @XStreamAlias("loginLogId") private int loginLogId; @XStreamAlias("userId") private int userId; @XStreamAlias("ip") private String ip; @XStreamConverter(DateConvert.class) private Date loginDate; } }
其中@XStreamAlias为别名注解,一般作用于目标类或字段,如实例中的@XStreamAlias("user"),@XStreamAlias("id")。@XStreamConvert注解用于注入自定义的转换器,如实例中的@XStreamConverter(DateConvert.class),注入一个日期转换器。对于集合类型,可以使用@XStreamImplicit注解来隐藏,其作用与XStream#addImplicitCollection()方法一样。@XStreamAsAttribute注解将Java对象属性映射为XML元素的一个属性。@XStreamOmitField注解标注Java对象的属性将不再出现在XMl中。
-
流化对象
XStream为java.io.ObjectInputStream和java.io.ObjectOutputStream提供替换的实现,允许以对象流的方式进行XML序列化和反序列化操作。这对于处理集合对象非常有用(List<USer> users),在内存中只保留一个User对象流。很显然,我们应该使用基于流而非DOM的XML解析器读取XML,以提高性能。
创建一个输出流,至于怎么输出可以使用多种方法,其原理是一样的。在这里就不得不提HierarchicalStreamWriter。HierarchicalStreamWriter是一个接口,从字面意思上来说它是由层级关系的输出流。XStream默认提供了几个常用的实现类用于输出,如CompactWrite,PrettyPrintWriter。下面应用XStream流化对象来处理XML序列化及反序列化。
public class XstreamSample { private static XStream xStream; static { xStream = new XStream(new DomDriver()); //实现该方法,判断要转换的类型 xStream.processAnnotations(User.class); xStream.processAnnotations(LoginLog.class); //自动加载注解Bean xStream.autodetectAnnotations(true); } public static User getUser() { LoginLog loginLog = new LoginLog(); loginLog.setIp("192.168.1.91"); loginLog.setLoginLogId(1); loginLog.setLoginDate(new Date()); User user = new User(); user.setUserName("wsq"); user.setUserId(1); user.setLogs(Lists.newArrayList(loginLog)); return user; } public static void main(String args[]) { try { objectToXml(); xmlToObject(); } catch (Exception e) { System.out.println("转换异常" + e); } } public static void objectToXml() throws Exception { User user = getUser(); PrintWriter pw = new PrintWriter("/Users/wsq/Desktop/XstreamSample.xml"); PrettyPrintWriter ppw = new PrettyPrintWriter(pw); // CompactWriter cw = new CompactWriter(pw); ObjectOutputStream out = xStream.createObjectOutputStream(ppw); out.writeObject(user); out.close(); } public static void xmlToObject() throws Exception { FileReader reader = new FileReader("/Users/wsq/Desktop/XstreamSample.xml"); BufferedReader bufferedReader = new BufferedReader(reader); ObjectInputStream input = xStream.createObjectInputStream(bufferedReader); User user = (User) input.readObject(); for (LoginLog login : user.getLogs()) { System.out.println(login); } } @Data @XStreamAlias("user") public static class User { @XStreamAsAttribute @XStreamAlias("id") private int userId; @XStreamAlias("userName") private String userName; @XStreamAlias("password") private String password; @XStreamAlias("credits") private int credits; @XStreamAlias("lastIp") private String lastIp; @XStreamConverter(DateConvert.class) private Date lastVisit; @XStreamImplicit private List<LoginLog> logs; } @Data public static class LoginLog { @XStreamAlias("loginLogId") private int loginLogId; @XStreamAlias("userId") private int userId; @XStreamAlias("ip") private String ip; @XStreamConverter(DateConvert.class) private Date loginDate; } }
public class DateConvert implements Converter { private Locale locale; public DateConvert() { super(); this.locale = Locale.forLanguageTag("CN"); } public DateConvert(Locale locale) { super(); this.locale = locale; } //编写Java对象到 XML的转换逻辑 @Override public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { DateFormat formatter = DateFormat.getDateInstance(DateFormat.DATE_FIELD, this.locale); writer.setValue(formatter.format(value)); } //编写 XML 到 java的转换逻辑 @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { GregorianCalendar calendar = new GregorianCalendar(); DateFormat formatter = DateFormat.getDateInstance(DateFormat.DATE_FIELD, this.locale); try { calendar.setTime(formatter.parse(reader.getValue())); } catch (Exception e) { throw new ConversionException(e.getMessage(), e); } return calendar.getGregorianChange(); } //判断要转换的类型 @Override public boolean canConvert(Class aClass) { return Date.class.isAssignableFrom(aClass); } }
-
持久化API
如果需要将一个集合中的所有对象持久化到文件系统中,通常会使用java.io的API将集合的对象逐个输入到文件中。虽然这个方法简单,但过程比较复杂。XStream提供了相关集合类的接口实现类,如 XmlArrayList,XmlSet,XmlSet,用一个简单的方法就可以将集合中的每个对象持久化到文件中。下面应用XmlArrayList来持久化一个集合中的所有对象,代码如下:
public void persist() { List<User> users = Lists.newArrayList(getUser()); File file = new File("/Users/wsq/Desktop/XstreamSample.xml"); PersistenceStrategy strategy = new FilePersistenceStrategy(file); List list = new XmlArrayList(strategy); list.addAll(users); }
Xstream为XmlArrayList,XmlSet,XmlMap实现类提供统一的创建接口,在创建XmlArrayList(及相关集合)的时候,需要指定一个持久化策略PersistenceStrategy。XStream提供一个持久化到文件的策略 FilePersistenceStrategy。这个策略将集合中每个对象持久化到指定目录不同的文件中
-
处理JSON
目前,在Web开发领域,主要的数据交换格式有XML,JSON,相信每个Web开发者对二者都不会感到mos。
JSON是一种轻量级的数据交换格式,易于阅读和编写,同时也易于及机器解析和生成。JSON采用完全独立于语言的文本格式,但是也使用来类似高级语言的一些习惯(如 java,C#等),这些特性使JSON成为理想的数据交换语言。虽然目前XML是业界数据交换的标准,在可扩展性,数据类型的描述方面有着明显优势,但是相比JSON这种轻量级的数据交换格式,XML显得有些笨重。
对于大多数Web应用来说,根本不需要复杂的XML来传输数据,XML宣称的扩展性在此没有多大的优势。大多数AJAX应用直接使用JSON发送个接收数据,以构建动态页面的内容,非常灵活易用。如果使用XML,则程序代码将会复杂不少。当然,在 Web Sercvice领域中,XML目前任然有不可动摇的地位。
在Java的世界里面有不少处理Java对象与JSON,XML相互转换的组件,如JSON-lib,XMLBeans。但同时支持两种数据格式转换的轻量级组件并不多,XStream组件就是少数中的佼佼者。
XStream的JettisonMappedXmlDriver和JsonHierarchicalStreamDriver都可以很好地完成Java对象和JSON的相互转换工作。值得注意的是,在使用JettisonMappedXmlDriver时,必须事先将jettison依赖包添加到工程pom中
<dependency> <groupId>org.codehaus.jettison</groupId> <artifactId>jettison</artifactId> <version>1.3.2</version> </dependency>
转换代码如下:
public class XstreamJsonSample { private static XStream xStream; public static void main(String args[]) { try { toJsonByJettisonMappedXmlDriver(); toJsonHierarchicalStreamDriver(); } catch (Exception e) { System.err.println("转换JSON 报错" + e); } } //生成没有分隔符的 JSON public static void toJsonByJettisonMappedXmlDriver() throws Exception { User user = getUser(); FileOutputStream outputStream = new FileOutputStream("/Users/wsq/Desktop/JettisonMappedXmlDriver.json"); OutputStreamWriter writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8")); xStream = new XStream(new JettisonMappedXmlDriver()); xStream.setMode(XStream.NO_REFERENCES); xStream.alias("user", User.class); xStream.toXML(user, writer); } //生成格式正常的JSON public static void toJsonHierarchicalStreamDriver() throws Exception { User user = getUser(); FileOutputStream outputStream = new FileOutputStream("/Users/wsq/Desktop/JsonHierarchicalStreamDriver.json"); OutputStreamWriter writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8")); xStream = new XStream(new JsonHierarchicalStreamDriver()); xStream.setMode(XStream.NO_REFERENCES); xStream.alias("user", User.class); xStream.toXML(user, writer); } public static User getUser() { LoginLog loginLog = new LoginLog(); loginLog.setIp("192.168.1.91"); loginLog.setLoginLogId(1); loginLog.setLoginDate(new Date()); User user = new User(); user.setUserName("wsq"); user.setUserId(1); user.setLogs(Lists.newArrayList(loginLog)); return user; } @Data public static class User { private int userId; private String userName; private String password; private int credits; private String lastIp; private Date lastVisit; private List<LoginLog> logs; } @Data public static class LoginLog { private int loginLogId; private int userId; private String ip; private Date loginDate; } }
JSON的转换和XML的转换用法一样,只需要在创建XStream实例时,传递一个XML到JSON映射转换的驱动器如JettisonMappedXmlDriver,JsonHierarchicalStreamDriver即可。使用JettisonMappedXmlDriver驱动生成的是一个连续的没有分隔符的JSON串,而使用JsonHierarchicalStreamDriver驱动器生成一个格式化后的JSON串。如果将JSON转换为对象,则只能使用JettisonMappedXmlDriver驱动。
生成的JSON如下:
{"user":{"userId":1,"userName":"wsq","credits":0,"logs":[{"cn.com.servyou.xqy.gaia.facadeimpl.XstreamJsonSample$LoginLog":{"loginLogId":1,"userId":0,"ip":"192.168.1.91","loginDate":"2020-06-14 12:37:26.856 UTC"}}]}}
{"user": { "userId": 1, "userName": "wsq", "credits": 0, "logs": [ { "loginLogId": 1, "userId": 0, "ip": "192.168.1.91", "loginDate": "2020-06-14 12:37:27.272 UTC" } ] }}
网友评论