【我在项目组使用的是xstream-1.4.3.jar】
最近在项目中遇到了JAVA bean 和XML互转的需求, 本来准备循规蹈矩使用dom4j忽然想起来之前曾接触过的XStream,一番研究豁然开朗,利器啊利器, 下来就XStream的一些用法与大家分享。
XStream是大名鼎鼎的thought works下的一个开源项目, 主要功能是提供JAVA bean 和XML文本之间的转换,另外还提供JAVA bean和JSON之间的转换,这个不在本次讨论的范围内。
XStream进行转换是非常简单的,对JAVA bean没有任何要求:
- 不要求对private属性提供access方法(set/get)。
- 不要求提供默认构造函数。
实际的
代码操作就更简单了,在JAVA1.5以后XSteam也
支持了annotation。 这时就只要在JAVA BEAN中
添加若干annotation就可以了。
- 基本转换
- 对象起别名
- 处理属性
- 处理List
- 忽略field
一、JavaBean和XML互转【注解方式】
1. 基本转换
这是一个普通的JAVA bean:
package xstreamTest;
public class Person {
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
转换代码是这样的:
XStream xstream = new XStream();
Person person = new Person();
person.setName("pli");
person.setAge(18);
System.out.println(xstream.toXML(person));
我们得到了这样的结果:
<xstreamTest.Person>
<name>pli</name>
<age>18</age>
</xstreamTest.Person>
有没有觉得很奇怪为什么会有“xstreamTest.Person”的
标签?对照下上面提到的JAVA bean,这个
标签是来自于JAVA bean的类全路径。
可是这个并不是我想要的啊,有没办法改变?有,简单吗? 简单!
2. 起别名
假如我们希望将“xstreamTest.Person” 这个莫名其妙的element标签改为“person”我们应该这么做:
package xstreamTest;
@XStreamAlias("person")
public class Person {
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
而执行代码会变成这样:
XStream xstream = new XStream();
xstream.autodetectAnnotations(true);
Person person = new Person();
person.setName("pli");
person.setAge(18);
System.out.println(xstream.toXML(person));
这样我们就得到了想要的:
<person>
<name>pli</name>
<age>18</age>
</person>
这里要提到的是“xstream.autodetectAnnotations(true);” 这句
代码告诉XStream去解析JAVA bean中的annotation。这句
代码有一个隐患,会在后面讨论。
别名可以改变任何你想在序列化时改变的对象名字,
包括:类,
属性甚至包名,所用到的其实就是“XSstreamAlias”这个annotation。
如果想要将JAVA bean中的“age”
属性作为XML中person
标签的一个attribute该怎么办呢。
这里介绍另外一个annotation:@XStreamAsAttribute,我们的JAVA bean变成了这样:
@XStreamAlias("person")
public class Person {
private String name;
@XStreamAsAttribute
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
结果是这样的:
<person age="18">
<name>pli</name>
</person>
好玩吧。
4. 处理List
如果JAVA bean中有List又是什么情形呢。
@XStreamAlias("person")
public class Person {
private String name;
@XStreamAsAttribute
private int age;
List<String> girlFriends;
public List<String> getGirlFriends() {
return girlFriends;
}
public void setGirlFriends(List<String> girlFriends) {
this.girlFriends = girlFriends;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
直接转换我们会得到这样的结果:
<person age="18">
<name>pli</name>
<girlFriends>
<string>YuanYuanGao</string>
<string>QiShu</string>
<string>BoZhiZhang</string>
</girlFriends>
</person>
结果其实也不赖,XStream在这里提供了一个@XStreamImplicit(itemFieldName=***)的annotation来满足用户想将List的根节点去掉和改变列表名字的需求,对应到我们的例子上就是去掉<girlFriends>标签和改变"<string>".我们来看看效果。
@XStreamAlias("person")
public class Person {
private String name;
@XStreamAsAttribute
private int age;
@XStreamImplicit(itemFieldName="girl")
List<String> girlFriends;
public List<String> getGirlFriends() {
return girlFriends;
}
public void setGirlFriends(List<String> girlFriends) {
this.girlFriends = girlFriends;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
结果是这样:
<person age="18">
<name>pli</name>
<girl>YuanYuanGao</girl>
<girl>QiShu</girl>
<girl>BoZhiZhang</girl>
</person>
5. 忽略属性
如果在JAVA bean中有些属性不想被序列化,XStream提供了解决这个需求的annotation: @XStreamOmitField
<person age="18">
<name>pli</name>
<birthday>2012-50-04</birthday>
</person>
另外在这里简单说说converter的原理:
其实XStream转换过程就是执行一个个converter的过程,只不过使用的大部分converter都是内建好的,XStream遇到一个待转换的object首先去查找能够转换这个object的转换器(converter),怎么找呢,就是通过converter的canConvert(Class clazz)这个
方法,返回为true就是可以转换。明白了吧。
二、JavaBean和XML互转【非
注解方式
】
准备的JavaBean如下:
Person javabean类
public class Person {
private String firstname;
private String lastname;
private PhoneNumber phone;
private PhoneNumber fax;
public Person() {
super();
}
public Person(String firstname,String lastname) {
super();
this.firstname = firstname;
this.lastname = lastname;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public PhoneNumber getPhone() {
return phone;
}
public void setPhone(PhoneNumber phone) {
this.phone = phone;
}
public PhoneNumber getFax() {
return fax;
}
public void setFax(PhoneNumber fax) {
this.fax = fax;
}
@Override
public String toString() {
return "Person [firstname=" + firstname + ",lastname=" + lastname
+ ",phone=" + phone + ",fax=" + fax + "]";
}
}
PhoneNumber javabean类
public class PhoneNumber {
private int code;
private String number;
public PhoneNumber() {
super();
}
public PhoneNumber(int code,String number) {
super();
this.code = code;
this.number = number;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
return "PhoneNumber [ code =" + code + ",number=" + number + "]";
}
转换测试类
1、将JavaBean转换为XML字符串
public class ConvertTest {
/**
* 将Person转换成xml字符串
*/
@Test
public void personToXML() {
XStream xstream = new XStream();
xstream.alias("person",Person.class);
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);
System.out.println(xml);
}
}
xstream.alias("person",Person.class);
xstream.alias("phonenumber",PhoneNumber.class);
这两句是给对象
加上别名,等价于注解方式里面的@XStreamAlias("person")
标签。
<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>
2、将xml字符串转换成JavaBean
@Test
public void xmlToPerson() {
String site = "CAN";
XStream xstream = new XStream();
xstream.alias("person",PhoneNumber.class);
String 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>";
xstream.omitField(Person.class,site);
Person newJoe = (Person) xstream.fromXML(xml);
System.out.println(newJoe.toString());
}
三、XStream的限制:
Xstream已经是很不错的东西了,如果真要找不足,我发现有两点。
1. 反序列化的时候无法使用autodetectAnnotations()
方法通知XStream对象去识别annotation。
还记的前面
代码中xstream.autodetectAnnotations(true); 吗, 这句
代码的意思是告诉XStream对象需要
自动识别annotation, 这在序列化(JAVA bean-->XML)的时候没什么问题。但是在反序列化的时候就有问题了,原因官网上说的比较模糊,总之就是不行,只能通过xstream.processAnnotations(Class clazz) 来显式的
注册需要使用annotation的类才行,如果JAVA bean很多就会比较麻烦。但一般来说JAVA bean在
代码组织结构中都比较集中,如放在听一个package下,这样也好办,可以在程序中将该package下的JAVA bean都
获取,然后使用xstream.processAnnotations(Class[] clazzs) 批量
注册。
2. Null
属性无法被序列化,即
值为空的字段是不会输出到XML的。
之前举的例子JAVA bean中的
属性都是被初始化以后才进行序列化的,如果没有初始化就进行序列化会怎样呢 ,还是举个例子
@XStreamAlias("person")
public class Person {
private String name = "pli";
@XStreamAsAttribute
private int age = 19;
@XStreamImplicit(itemFieldName="girl")
@XStreamOmitField
List<String> girlFriends;
@XStreamConverter(value=DateConverter.class)
Date birthday = new Date();
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public List<String> getGirlFriends() {
return girlFriends;
}
public void setGirlFriends(List<String> girlFriends) {
this.girlFriends = girlFriends;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
我想将其它属性都进行了初始化但是没有将girlFriends这个属性初始化,即使说girlFriends==null. 序列化以后会怎样呢?
<person age="18">
<name>pli</name>
<birthday>2012-36-04</birthday>
<girlFriends/>
</person>
有什么办法没,真没啥办法。我查了查源码,确实如果某个
属性为null的话就不进行序列化的,唯一的办法是
修改源码,这个太费事。
另外提一点,XStream也提供了不使用annotation的方式,有兴趣请在XStream的官网上查看。
四、xmlUtils--【xml和javaBean互转工具类】
package com.openeap.webservice.base;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.openeap.modules.taskBoard.entity.TaskBoardSjToQd;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
public class XmlUtil {
private static Logger log = LoggerFactory.getLogger(XmlUtil.class);
/**
* java 转换成xml
*
* @Title: toXml
* @Description: TODO
* @param obj
* 对象实例
* @return String xml字符串
*/
public static String toXml(Object obj) {
XStream xstream = new XStream();
// XStream xstream=new XStream(new DomDriver()); //直接用jaxp dom来解释
// XStream xstream=new XStream(new DomDriver("utf-8"));
// //指定编码解析器,直接用jaxp dom来解释
// //如果没有这句,xml中的根元素会是<包.类名>;或者说:注解根本就没生效,所以的元素名就是类的属性
xstream.processAnnotations(obj.getClass()); // 通过注解方式的,一定要有这句话
return xstream.toXML(obj);
}
/**
* 将传入xml文本转换成Java对象
*
* @Title: toBean
* @Description: TODO
* @param xmlStr
* @param cls
* xml对应的class类
* @return T xml对应的class类的实例对象
*
* 调用的方法实例:PersonBean person=XmlUtil.toBean(xmlStr,* PersonBean.class);
*/
public static <T> T toBean(String xmlStr,Class<T> cls) {
// 注意:不是new Xstream(); 否则报错:java.lang.NoClassDefFoundError:
// org/xmlpull/v1/XmlPullParserFactory
XStream xstream = new XStream(new DomDriver());
xstream.alias(cls.getSimpleName(),cls);
//xstream.processAnnotations(cls);
T obj = (T) xstream.fromXML(xmlStr);
return obj;
}
/**
* 写到xml文件中去
*
* @Title: writeXMLFile
* @Description: TODO
* @param obj
* 对象
* @param absPath
* 绝对路径
* @param fileName
* 文件名
* @return boolean
*/
public static boolean toXMLFile(Object obj,String absPath,String fileName) {
String strXml = toXml(obj);
String filePath = absPath + fileName;
File file = new File(filePath);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
log.error("创建{" + filePath + "}文件失败!!!");
return false;
}
}// end if
OutputStream ous = null;
try {
ous = new FileOutputStream(file);
ous.write(strXml.getBytes());
ous.flush();
} catch (Exception e1) {
log.error("写{" + filePath + "}文件失败!!!");
return false;
} finally {
if (ous != null)
try {
ous.close();
} catch (IOException e) {
log.error("写{" + filePath + "}文件关闭输出流异常!!!");
}
}
return true;
}
/**
* 从xml文件读取报文
*
* @Title: toBeanFromFile
* @Description: TODO
* @param absPath
* 绝对路径
* @param fileName
* 文件名
* @param cls
* @throws Exception
* @return T
*/
public static <T> T toBeanFromFile(String filePath,Class<T>[] cls) throws Exception {
//String filePath = absPath + fileName;
InputStream ins = null;
try {
ins = new FileInputStream(new File(filePath));
} catch (Exception e) {
throw new Exception("读{" + filePath + "}文件失败!",e);
}
String encode = "UTF-8";// useEncode(cls);
XStream xstream = new XStream(new DomDriver(encode));
xstream.processAnnotations(cls);
/*for(Class tempCls : cls){
xstream.alias(tempCls.getSimpleName(),tempCls);
}*/
T obj = null;
try {
obj = (T) xstream.fromXML(ins);
} catch (Exception e) {
// TODO Auto-generated catch block
throw new Exception("解析{" + filePath + "}文件失败!",e);
}
if (ins != null)
ins.close();
return obj;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}