最近在項目中遇到了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就可以了,當然如果不允許修改JAVA bean, 那XStream也提供register的方式,也是很簡單的。 我準備在例子中展現一下的topic:
- 基本轉換
- 對象起别名
- 處理屬性
- 處理List
- 忽略field
1. 基本轉換 這是一個普通的JAVA bean: [java] view plain copy
- 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;
- }
- }
轉換代碼是這樣的: [java] view plain copy
- XStream xstream = new XStream();
- Person person = new Person();
- person.setName("pli");
- person.setAge(18);
- System.out.println(xstream.toXML(person));
我們得到了這樣的結果: [html] view plain copy
- <xstreamTest.Person>
- <name>pli</name>
- <age>18</age>
- </xstreamTest.Person>
有沒有覺得很奇怪為什麼會有“xstreamTest.Person”的标簽?對照下上面提到的JAVA bean這個标簽是來自于JAVA bean的類全路徑的。 可是這個并不是我想要的啊,有沒辦法改變?有,簡單嗎? 簡單!
2. 起别名 家丁我們希望将“xstreamTest.Person” 這個莫名其妙的element标簽改為“person”我們應該這麼做。 [java] view plain copy
- 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;
- }
- }
而執行代碼會變成這樣: [java] view plain copy
- XStream xstream = new XStream();
- xstream.autodetectAnnotations(true);
- Person person = new Person();
- person.setName("pli");
- person.setAge(18);
- System.out.println(xstream.toXML(person));
這樣我們就得到了想要的: [html] view plain copy
- <person>
- <name>pli</name>
- <age>18</age>
- </person>
這裡要提到的是“xstream.autodetectAnnotations(true);” 這句代碼告訴XStream去解析JAVA bean中的annotation。這句代碼有一個隐患,會在後面讨論。 别名可以改變任何你想在序列化時改變的對象名字,類,屬性甚至包名,所用到的其實就是“XSstreamAlias”這個annotation。
3. 處理屬性 如果想要将JAVA bean中的“age”屬性作為XML中person标簽的一個attribute該怎麼辦呢。 這裡介紹另外一個annotation:@XStreamAsAttribute, 我們的JAVA bean變成了這樣: [java] view plain copy
- @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;
- }
- }
結果是這樣的: [html] view plain copy
- <person age="18">
- <name>pli</name>
- </person>
好玩吧。
4. 處理List 如果JAVA bean中有List是什麼情形呢。 [java] view plain copy
- @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;
- }
- }
直接轉換我們會得到這樣的結果: [html] view plain copy
- <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>".我們來看看效果。 [java] view plain copy
- @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;
- }
- }
結果是這樣: [html] view plain copy
- <person age="18">
- <name>pli</name>
- <girl>YuanYuanGao</girl>
- <girl>QiShu</girl>
- <girl>BoZhiZhang</girl>
- </person>
5. 忽略屬性 如果在JAVA bean中有些屬性不想被序列化,XStream提供了解決這個需求的annotation: @XStreamOmitField 比如說不想講girlfriends這個List序列化 [java] view plain copy
- @XStreamAlias("person")
- public class Person {
- private String name;
- @XStreamAsAttribute
- private int age;
- @XStreamImplicit(itemFieldName="girl")
- @XStreamOmitField
- 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;
- }
- }
結果是這樣: [html] view plain copy
- <person age="18">
- <name>pli</name>
- </person>
6. Converter
Converter這個是屬于XStream中的進階特性了,用于基本功能不能滿足的情況下讓客戶自己定制序列化/反系列化的細節,我們還是通過一個例子進行說明。 假如我要往JAVA bean中添加一個類型為Date的屬性: [java] view plain copy
- @XStreamAlias("person")
- public class Person {
- private String name;
- @XStreamAsAttribute
- private int age;
- @XStreamImplicit(itemFieldName="girl")
- @XStreamOmitField
- List<String> girlFriends;
- Date birthday;
- 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;
- }
- }
看看直接序列化的結果: [html] view plain copy
- <person age="18">
- <name>pli</name>
- <birthday>2012-08-04 04:35:01.857 UTC</birthday>
- </person>
還不錯,但是生日隻需要年月日就行了,沒必要精确到毫秒,這怎麼辦呢,隻能使用converter,我們這是就需要寫代碼了。 [java] view plain copy
- public class DateConverter implements Converter {
- @Override
- public boolean canConvert(Class clazz) {
- return (Date.class).equals(clazz);
- }
- @Override
- public void marshal(Object object, HierarchicalStreamWriter writer,
- MarshallingContext context) {
- Date date = (Date) object;
- Calendar calendar = Calendar.getInstance();
- calendar.setTime(date);
- SimpleDateFormat format = new SimpleDateFormat("yyyy-mm-dd");
- writer.setValue(format.format(calendar.getTime()));
- }
- @Override
- public Object unmarshal(HierarchicalStreamReader arg0,
- UnmarshallingContext arg1) {
- return null;
- }
- }
稍微解釋下這段代碼:DateConverter 實作了借口Converter,實作了接口中的三個方法:
- public boolean canConvert(Class clazz) 用來檢測本converter是否能夠轉換輸入的類型。
- public void marshal(Object object, HierarchicalStreamWriter writer,MarshallingContext context) 序列化的方法(JAVA bean --> XML)
- public Object unmarshal(HierarchicalStreamReader arg0, UnmarshallingContext arg1) 反序列化的方法。因為本例用不到是以沒有實作。
此時我們的JAVA bean也要相應改變: [java] view plain copy
- @XStreamAlias("person")
- public class Person {
- private String name;
- @XStreamAsAttribute
- private int age;
- @XStreamImplicit(itemFieldName="girl")
- @XStreamOmitField
- List<String> girlFriends;
- @XStreamConverter(value=DateConverter.class)
- Date birthday;
- 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;
- }
- }
看看結果: [html] view plain copy
- <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就是可以轉換。明白了吧。
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 屬性無法被序列化。 之前舉的例子JAVA bean中的屬性都是被初始化以後才進行序列化的,如果沒有初始化就進行序列化會怎樣呢 ,還是舉個例子 [java] view plain copy
- @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. 序列化以後會怎樣呢?
[html] view plain copy
- <person age="18">
- <name>pli</name>
- <birthday>2012-36-04</birthday>
- </person>
girlFriends這個屬性壓根就沒有被序列化,其實我是想讓它序列化成這個樣子: [html] view plain copy
- <person age="18">
- <name>pli</name>
- <birthday>2012-36-04</birthday>
- <girlFriends/>
- </person>
有什麼辦法沒,真沒啥辦法。我查了查源碼,确實如果某個屬性為null的話就不進行序列化的,唯一的辦法是修改源碼,這個太費事,如果你有興趣請參看這個連結上的文章,會有幫助: 點選打開連結 另外提一點,XStream也提供了不适用annotation的方式,有興趣請在XStream的官網上檢視。 點選打開連結
好了,寫的夠多了,都比較淺顯,有什麼疑問請告之。