1. 程式人生 > >JAVA bean與XML互轉的利器---XStream

JAVA bean與XML互轉的利器---XStream

pub 普通 ati mat his cit true 是我 package

最近在項目中遇到了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:
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。 3. 處理屬性 如果想要將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 比如說不想講girlfriends這個List序列化
@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;  
    }  
}  

結果是這樣:

<person age="18">  
  <name>pli</name>  
</person> 
6. Converter Converter這個是屬於XStream中的高級特性了,用於基本功能不能滿足的情況下讓客戶自己定制序列化/反系列化的細節,我們還是通過一個例子進行說明。 假如我要往JAVA bean中添加一個類型為Date的屬性:
@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;  
    }  
}  

看看直接序列化的結果:

<person age="18">  
  <name>pli</name>  
  <birthday>2012-08-04 04:35:01.857 UTC</birthday>  
</person>

還不錯,但是生日只需要年月日就行了,沒必要精確到毫秒,這怎麽辦呢,只能使用converter,我們這是就需要寫代碼了。

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也要相應改變:
@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;  
    }  
}  

看看結果:

<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中的屬性都是被初始化以後才進行序列化的,如果沒有初始化就進行序列化會怎樣呢 ,還是舉個例子
@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>  
</person>

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

<person age="18">  
  <name>pli</name>  
  <birthday>2012-36-04</birthday>  
  <girlFriends/>  
</person>  

原文:http://blog.csdn.net/pushme_pli/article/details/7829621

JAVA bean與XML互轉的利器---XStream