天天看點

【轉】xstream的使用介紹(javabean和xml互轉)

最近在項目中遇到了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

  1. package xstreamTest;  
  2. public class Person {  
  3.     private String name;  
  4.     private int age;  
  5.     public int getAge() {  
  6.         return age;  
  7.     }  
  8.     public void setAge(int age) {  
  9.         this.age = age;  
  10.     }  
  11.     public void setName(String name) {  
  12.         this.name = name;  
  13.     }  
  14.     public String getName() {  
  15.         return this.name;  
  16.     }  
  17. }  

轉換代碼是這樣的: [java]  view plain copy

  1. XStream xstream = new XStream();  
  2. Person person = new Person();  
  3. person.setName("pli");  
  4. person.setAge(18);  
  5. System.out.println(xstream.toXML(person));  

我們得到了這樣的結果: [html]  view plain copy

  1. <xstreamTest.Person>  
  2.   <name>pli</name>  
  3.   <age>18</age>  
  4. </xstreamTest.Person>  

有沒有覺得很奇怪為什麼會有“xstreamTest.Person”的标簽?對照下上面提到的JAVA bean這個标簽是來自于JAVA bean的類全路徑的。 可是這個并不是我想要的啊,有沒辦法改變?有,簡單嗎? 簡單!

2. 起别名 家丁我們希望将“xstreamTest.Person” 這個莫名其妙的element标簽改為“person”我們應該這麼做。 [java]  view plain copy

  1. package xstreamTest;  
  2. @XStreamAlias("person")  
  3. public class Person {  
  4.     private String name;  
  5.     private int age;  
  6.     public int getAge() {  
  7.         return age;  
  8.     }  
  9.     public void setAge(int age) {  
  10.         this.age = age;  
  11.     }  
  12.     public void setName(String name) {  
  13.         this.name = name;  
  14.     }  
  15.     public String getName() {  
  16.         return this.name;  
  17.     }  
  18. }  

而執行代碼會變成這樣: [java]  view plain copy

  1. XStream xstream = new XStream();  
  2. xstream.autodetectAnnotations(true);  
  3. Person person = new Person();  
  4. person.setName("pli");  
  5. person.setAge(18);  
  6. System.out.println(xstream.toXML(person));  

這樣我們就得到了想要的: [html]  view plain copy

  1. <person>  
  2.   <name>pli</name>  
  3.   <age>18</age>  
  4. </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

  1. @XStreamAlias("person")  
  2. public class Person {  
  3.     private String name;  
  4.     @XStreamAsAttribute  
  5.     private int age;  
  6.     public int getAge() {  
  7.         return age;  
  8.     }  
  9.     public void setAge(int age) {  
  10.         this.age = age;  
  11.     }  
  12.     public void setName(String name) {  
  13.         this.name = name;  
  14.     }  
  15.     public String getName() {  
  16.         return this.name;  
  17.     }  
  18. }  

結果是這樣的: [html]  view plain copy

  1. <person age="18">  
  2.   <name>pli</name>  
  3. </person>  

好玩吧。

4. 處理List 如果JAVA bean中有List是什麼情形呢。 [java]  view plain copy

  1. @XStreamAlias("person")  
  2. public class Person {  
  3.     private String name;  
  4.     @XStreamAsAttribute  
  5.     private int age;  
  6.     List<String> girlFriends;  
  7.     public List<String> getGirlFriends() {  
  8.         return girlFriends;  
  9.     }  
  10.     public void setGirlFriends(List<String> girlFriends) {  
  11.         this.girlFriends = girlFriends;  
  12.     }  
  13.     public int getAge() {  
  14.         return age;  
  15.     }  
  16.     public void setAge(int age) {  
  17.         this.age = age;  
  18.     }  
  19.     public void setName(String name) {  
  20.         this.name = name;  
  21.     }  
  22.     public String getName() {  
  23.         return this.name;  
  24.     }  
  25. }  

直接轉換我們會得到這樣的結果: [html]  view plain copy

  1. <person age="18">  
  2.   <name>pli</name>  
  3.   <girlFriends>  
  4.     <string>YuanYuanGao</string>  
  5.     <string>QiShu</string>  
  6.     <string>BoZhiZhang</string>  
  7.   </girlFriends>  
  8. </person>  

結果其實也不賴,XStream在這裡提供了一個@XStreamImplicit(itemFieldName=***)的annotation來滿足使用者想将List的根節點去掉和改變清單名字的需求,對應到我們的例子上就是去掉<girlFriends>标簽和改變"<string>".我們來看看效果。 [java]  view plain copy

  1. @XStreamAlias("person")  
  2. public class Person {  
  3.     private String name;  
  4.     @XStreamAsAttribute  
  5.     private int age;  
  6.     @XStreamImplicit(itemFieldName="girl")  
  7.     List<String> girlFriends;  
  8.     public List<String> getGirlFriends() {  
  9.         return girlFriends;  
  10.     }  
  11.     public void setGirlFriends(List<String> girlFriends) {  
  12.         this.girlFriends = girlFriends;  
  13.     }  
  14.     public int getAge() {  
  15.         return age;  
  16.     }  
  17.     public void setAge(int age) {  
  18.         this.age = age;  
  19.     }  
  20.     public void setName(String name) {  
  21.         this.name = name;  
  22.     }  
  23.     public String getName() {  
  24.         return this.name;  
  25.     }  
  26. }  

結果是這樣: [html]  view plain copy

  1. <person age="18">  
  2.   <name>pli</name>  
  3.   <girl>YuanYuanGao</girl>  
  4.   <girl>QiShu</girl>  
  5.   <girl>BoZhiZhang</girl>  
  6. </person>  

5. 忽略屬性 如果在JAVA bean中有些屬性不想被序列化,XStream提供了解決這個需求的annotation: @XStreamOmitField 比如說不想講girlfriends這個List序列化 [java]  view plain copy

  1. @XStreamAlias("person")  
  2. public class Person {  
  3.     private String name;  
  4.     @XStreamAsAttribute  
  5.     private int age;  
  6.     @XStreamImplicit(itemFieldName="girl")  
  7.     @XStreamOmitField  
  8.     List<String> girlFriends;  
  9.     public List<String> getGirlFriends() {  
  10.         return girlFriends;  
  11.     }  
  12.     public void setGirlFriends(List<String> girlFriends) {  
  13.         this.girlFriends = girlFriends;  
  14.     }  
  15.     public int getAge() {  
  16.         return age;  
  17.     }  
  18.     public void setAge(int age) {  
  19.         this.age = age;  
  20.     }  
  21.     public void setName(String name) {  
  22.         this.name = name;  
  23.     }  
  24.     public String getName() {  
  25.         return this.name;  
  26.     }  
  27. }  

結果是這樣: [html]  view plain copy

  1. <person age="18">  
  2.   <name>pli</name>  
  3. </person>  

6. Converter

Converter這個是屬于XStream中的進階特性了,用于基本功能不能滿足的情況下讓客戶自己定制序列化/反系列化的細節,我們還是通過一個例子進行說明。 假如我要往JAVA bean中添加一個類型為Date的屬性: [java]  view plain copy

  1. @XStreamAlias("person")  
  2. public class Person {  
  3.     private String name;  
  4.     @XStreamAsAttribute  
  5.     private int age;  
  6.     @XStreamImplicit(itemFieldName="girl")  
  7.     @XStreamOmitField  
  8.     List<String> girlFriends;  
  9.     Date birthday;  
  10.     public Date getBirthday() {  
  11.         return birthday;  
  12.     }  
  13.     public void setBirthday(Date birthday) {  
  14.         this.birthday = birthday;  
  15.     }  
  16.     public List<String> getGirlFriends() {  
  17.         return girlFriends;  
  18.     }  
  19.     public void setGirlFriends(List<String> girlFriends) {  
  20.         this.girlFriends = girlFriends;  
  21.     }  
  22.     public int getAge() {  
  23.         return age;  
  24.     }  
  25.     public void setAge(int age) {  
  26.         this.age = age;  
  27.     }  
  28.     public void setName(String name) {  
  29.         this.name = name;  
  30.     }  
  31.     public String getName() {  
  32.         return this.name;  
  33.     }  
  34. }  

看看直接序列化的結果: [html]  view plain copy

  1. <person age="18">  
  2.   <name>pli</name>  
  3.   <birthday>2012-08-04 04:35:01.857 UTC</birthday>  
  4. </person>  

還不錯,但是生日隻需要年月日就行了,沒必要精确到毫秒,這怎麼辦呢,隻能使用converter,我們這是就需要寫代碼了。 [java]  view plain copy

  1. public class DateConverter implements Converter {  
  2.     @Override  
  3.     public boolean canConvert(Class clazz) {  
  4.         return (Date.class).equals(clazz);  
  5.     }  
  6.     @Override  
  7.     public void marshal(Object object, HierarchicalStreamWriter writer,  
  8.             MarshallingContext context) {  
  9.         Date date = (Date) object;  
  10.         Calendar calendar = Calendar.getInstance();  
  11.         calendar.setTime(date);  
  12.         SimpleDateFormat format = new SimpleDateFormat("yyyy-mm-dd");  
  13.         writer.setValue(format.format(calendar.getTime()));  
  14.     }  
  15.     @Override  
  16.     public Object unmarshal(HierarchicalStreamReader arg0,  
  17.             UnmarshallingContext arg1) {  
  18.         return null;  
  19.     }  
  20. }  

稍微解釋下這段代碼: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

  1. @XStreamAlias("person")  
  2. public class Person {  
  3.     private String name;  
  4.     @XStreamAsAttribute  
  5.     private int age;  
  6.     @XStreamImplicit(itemFieldName="girl")  
  7.     @XStreamOmitField  
  8.     List<String> girlFriends;  
  9.     @XStreamConverter(value=DateConverter.class)  
  10.     Date birthday;  
  11.     public Date getBirthday() {  
  12.         return birthday;  
  13.     }  
  14.     public void setBirthday(Date birthday) {  
  15.         this.birthday = birthday;  
  16.     }  
  17.     public List<String> getGirlFriends() {  
  18.         return girlFriends;  
  19.     }  
  20.     public void setGirlFriends(List<String> girlFriends) {  
  21.         this.girlFriends = girlFriends;  
  22.     }  
  23.     public int getAge() {  
  24.         return age;  
  25.     }  
  26.     public void setAge(int age) {  
  27.         this.age = age;  
  28.     }  
  29.     public void setName(String name) {  
  30.         this.name = name;  
  31.     }  
  32.     public String getName() {  
  33.         return this.name;  
  34.     }  
  35. }  

看看結果: [html]  view plain copy

  1. <person age="18">  
  2.   <name>pli</name>  
  3.   <birthday>2012-50-04</birthday>  
  4. </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

  1. @XStreamAlias("person")  
  2. public class Person {  
  3.     private String name = "pli";  
  4.     @XStreamAsAttribute  
  5.     private int age = 19;  
  6.     @XStreamImplicit(itemFieldName="girl")  
  7.     @XStreamOmitField  
  8.     List<String> girlFriends;  
  9.     @XStreamConverter(value=DateConverter.class)  
  10.     Date birthday = new Date();  
  11.     public Date getBirthday() {  
  12.         return birthday;  
  13.     }  
  14.     public void setBirthday(Date birthday) {  
  15.         this.birthday = birthday;  
  16.     }  
  17.     public List<String> getGirlFriends() {  
  18.         return girlFriends;  
  19.     }  
  20.     public void setGirlFriends(List<String> girlFriends) {  
  21.         this.girlFriends = girlFriends;  
  22.     }  
  23.     public int getAge() {  
  24.         return age;  
  25.     }  
  26.     public void setAge(int age) {  
  27.         this.age = age;  
  28.     }  
  29.     public void setName(String name) {  
  30.         this.name = name;  
  31.     }  
  32.     public String getName() {  
  33.         return this.name;  
  34.     }  
  35. }  

我想将其它屬性都進行了初始化但是沒有将girlFriends這個屬性初始化,即使說girlFriends==null. 序列化以後會怎樣呢?

[html]  view plain copy

  1. <person age="18">  
  2.   <name>pli</name>  
  3.   <birthday>2012-36-04</birthday>  
  4. </person>  

girlFriends這個屬性壓根就沒有被序列化,其實我是想讓它序列化成這個樣子: [html]  view plain copy

  1. <person age="18">  
  2.   <name>pli</name>  
  3.   <birthday>2012-36-04</birthday>  
  4.   <girlFriends/>  
  5. </person>  

有什麼辦法沒,真沒啥辦法。我查了查源碼,确實如果某個屬性為null的話就不進行序列化的,唯一的辦法是修改源碼,這個太費事,如果你有興趣請參看這個連結上的文章,會有幫助: 點選打開連結 另外提一點,XStream也提供了不适用annotation的方式,有興趣請在XStream的官網上檢視。 點選打開連結

好了,寫的夠多了,都比較淺顯,有什麼疑問請告之。