六、使用註解(Annotation)
總是使用XStream對象的別名方法和註冊轉換器,會讓人感到非常的乏味,又會產生很多重複性代碼,於是我們可以使用註解的方式來配置要序列化的POJO對象。
1,最基本的註解:類的別名性註解和字段的別名性註解(XStreamAlias)
有這樣一段代碼:
import com.thoughtworks.xstream.XStream;
public class XStreamTest3 {
public static void main(String[] args) {
XStream stream = new XStream();
RendezvousMessage msg = new RendezvousMessage(15);
System.out.println(stream.toXML(msg));
}
}
class RendezvousMessage {
private int messageType;
public RendezvousMessage(int messageType) {
this.messageType = messageType;
}
}
運行結果是:
<cn.tjpu.zhw.xml.RendezvousMessage>
<messageType>15</messageType>
</cn.tjpu.zhw.xml.RendezvousMessage>
如果我們需要將輸出的XML文本是這樣:
<message>
<type>15</type>
</message>
該怎麼辦?
我們當然可以在main方法中調用XStream對象的別名映射方法進行處理,但我們也可以使用更簡單的註解的方式進行處理。
對RendezvousMessage類的定義進行註解如下:
//對類的別名性註解
@XStreamAlias("message")
class RendezvousMessage {
//對字段的別名性註解
@XStreamAlias("type")
private int messageType;
public RendezvousMessage(int messageType) {
this.messageType = messageType;
}
}
但是,我們進行註解之後發現輸出的結果並沒有改變,爲什麼?
因爲XStream對象默認是不讀取和識別註解的,需要我們主動提醒它,而後XStream對象才能在轉換的的時候讀取註解。
更改main方法如下:
public static void main(String[] args) {
XStream stream = new XStream();
//通知XStream對象讀取並識別RendezvousMessage中的註解
stream.processAnnotations(RendezvousMessage.class);
RendezvousMessage msg = new RendezvousMessage(15);
System.out.println(stream.toXML(msg));
}
這樣輸出的結果就能與預想的一樣了。
注意:當使用XStream對象處理一個被註解的類型時,XStream對象也會處理所有與其相關的類型的註解信息,即該類型的父類、父接口、所有子類的註解。
processAnnotations方法還有一個重載的方法,是以Class []作爲參數的。
2,隱式集合註解(XStreamImplicit)
現在我們給RendezvousMessage類添加一個List集合字段,並且更改一下RendezvousMessage的構造方法,新的代碼如下:
import java.util.Arrays;
import java.util.List;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
public class XStreamTest3 {
public static void main(String[] args) {
XStream stream = new XStream();
//通知XStream對象讀取並識別RendezvousMessage中的註解
stream.processAnnotations(RendezvousMessage.class);
RendezvousMessage msg = new RendezvousMessage(15,"first","second");
System.out.println(stream.toXML(msg));
}
}
//對類的別名性註解
@XStreamAlias("message")
class RendezvousMessage {
//對字段的別名性註解
@XStreamAlias("type")
private int messageType;
//新添加的集合字段
private List<String> content;
//經改造的構造方法
public RendezvousMessage(int messageType, String ... content) {
this.messageType = messageType;
this.content = Arrays.asList(content);
}
}
運輸出結果如下:
<message>
<type>15</type>
<content class="java.util.Arrays$ArrayList">
<a class="string-array">
<string>first</string>
<string>second</string>
</a>
</content>
</message>
但是,如果我們想讓輸出的XML格式如下:
<message>
<type>15</type>
<part>firstPart</part>
<part>secondPart</part>
</message>
該怎麼辦?
現在我們給集合字段添加隱式集合性註解,以去除集合的根節點:
//對類的別名性註解
@XStreamAlias("message")
class RendezvousMessage {
//對字段的別名性註解
@XStreamAlias("type")
private int messageType;
//隱式集合性註解
@XStreamImplicit
private List<String> content;
public RendezvousMessage(int messageType, String ... content) {
this.messageType = messageType;
this.content = Arrays.asList(content);
}
}
重新運行程序,輸出結果如下:
<message>
<type>15</type>
<string>first</string>
<string>second</string>
</message>
輸出的結果中,集合的每一個子節點的節點名都是string,現在需要將子節點的節點名改爲part,這樣就需要繼續更改註解項:
//對類的別名性註解
@XStreamAlias("message")
class RendezvousMessage {
//對字段的別名性註解
@XStreamAlias("type")
private int messageType;
//對隱式集合的註解,將每一個子節點的節點名都改爲part
@XStreamImplicit(itemFieldName="part")
private List<String> content;
public RendezvousMessage(int messageType, String ... content) {
this.messageType = messageType;
this.content = Arrays.asList(content);
}
}
這樣輸出的結果就能夠跟預想的一樣了,成功了!!!
注意:隱式集合註解同樣可以用於數組和Map對象。
3,註解轉換器(XStreamConverter)
現在我們再給RendezvousMessage類添加兩個字段,一個boolean字段和一個時間Calendar字段,代碼如下:
public class XStreamTest3 {
public static void main(String[] args) {
XStream stream = new XStream();
// 通知XStream對象讀取並識別RendezvousMessage中的註解
stream.processAnnotations(RendezvousMessage.class);
RendezvousMessage msg = new RendezvousMessage(15,false,"first","second");
System.out.println(stream.toXML(msg));
}
}
// 對類的別名性註解
@XStreamAlias("message")
class RendezvousMessage {
// 對字段的別名性註解
@XStreamAlias("type")
private int messageType;
// 隱式集合性註解
@XStreamImplicit(itemFieldName = "part")
private List<String> content;
private boolean important;
private Calendar created = new GregorianCalendar();
// 再次對構造方法進行了改造
public RendezvousMessage(int messageType, boolean important,
String... content) {
this.messageType = messageType;
this.important = important;
this.content = Arrays.asList(content);
}
}
運行結果如下:
<message>
<type>15</type>
<part>first</part>
<part>second</part>
<important>false</important>
<created>
<time>1387534087343</time>
<timezone>Asia/Shanghai</timezone>
</created>
</message>
現在,我們要將輸出結果改造爲:
<message>
<type>15</type>
<part>firstPart</part>
<part>secondPart</part>
<important>no</important>
<created>1379430873703</created>
</message>
該如何做?
首先,我們使用註解處理Calendar時間字段的轉換,先定義一個時間的轉換器SingleValueCalendarConverter,代碼如下:
package cn.tjpu.zhw.xml;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
//必須是public類型
public class SingleValueCalendarConverter implements Converter {
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
Calendar calendar = (Calendar) source;
writer.setValue(String.valueOf(calendar.getTime().getTime()));
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(new Date(Long.parseLong(reader.getValue())));
return calendar;
}
public boolean canConvert(Class type) {
return type.equals(GregorianCalendar.class);
}
}
然後,我們需要使用SingleValueCalendarConverter轉換器對Calendar字段進行註解:
//對類的別名性註解
@XStreamAlias("message")
class RendezvousMessage {
//對字段的別名性註解
@XStreamAlias("type")
private int messageType;
//對隱式集合的註解,將每一個子節點的節點名都改爲part
@XStreamImplicit(itemFieldName="part")
private List<String> content;
private boolean important;
//爲該字段的註解指定轉換器
@XStreamConverter(SingleValueCalendarConverter.class)
private Calendar created = new GregorianCalendar();
public RendezvousMessage(int messageType, String ... content) {
this.messageType = messageType;
this.content = Arrays.asList(content);
}
}
運行結果如下:
<message>
<type>15</type>
<part>first</part>
<part>second</part>
<important>false</important>
<created>1387534774062</created>
</message>
但是我們發現important節點中的內容是true或false,怎樣讓它變成yes或no呢?
我們可以使用框架爲我們提供的一個轉換器BooleanConverter
修改RendezvousMessage的類定義:
//對類別名的註解
@XStreamAlias("message")
class RendezvousMessage {
//對字段別名的註解
@XStreamAlias("type")
private int messageType;
//對隱式集合的註解,將每一個子節點的節點名都改爲part
@XStreamImplicit(itemFieldName="part")
private List<String> content;
//將true/false改爲yes/no
@XStreamConverter(value=BooleanConverter.class, booleans={false}, strings={"yes", "no"})
private boolean important;
//爲該字段添加轉換器註解
@XStreamConverter(SingleValueCalendarConverter.class)
private Calendar created = new GregorianCalendar();
public RendezvousMessage(int messageType, boolean important, String... content) {
this.messageType = messageType;
this.important = important;
this.content = Arrays.asList(content);
}
}
運行結果如下:
<message>
<type>15</type>
<part>first</part>
<part>second</part>
<important>no</important>
<created>1387534827609</created>
</message>
這正是我們想要的!!!!
4,屬性註解
現在我們想將上面的XML格式改造成爲:
<message type="15" important="no">
<part>firstPart</part>
<part>secondPart</part>
<created>1154097812245</created>
</message>
,也就是把type節點和important節點作爲父節點的屬性,該怎麼做?
答案是,使用屬性註解:
@XStreamAsAttribute
代碼如下:
public class XStreamTest3 {
public static void main(String[] args) {
XStream stream = new XStream();
// 通知XStream對象讀取並識別RendezvousMessage中的註解
stream.processAnnotations(RendezvousMessage.class);
RendezvousMessage msg = new RendezvousMessage(15, false, "first",
"second");
System.out.println(stream.toXML(msg));
}
}
// 對類的別名性註解
@XStreamAlias("message")
class RendezvousMessage {
//將type節點變成屬性
@XStreamAsAttribute
// 對字段的別名性註解
@XStreamAlias("type")
private int messageType;
// 隱式集合性註解
@XStreamImplicit(itemFieldName = "part")
private List<String> content;
//將important節點變成屬性
@XStreamAsAttribute
// 將true/false改爲yes/no
@XStreamConverter(value = BooleanConverter.class, booleans = { false }, strings = {
"yes", "no" })
private boolean important;
@XStreamConverter(SingleValueCalendarConverter.class)
private Calendar created = new GregorianCalendar();
public RendezvousMessage(int messageType, boolean important,
String... content) {
this.messageType = messageType;
this.important = important;
this.content = Arrays.asList(content);
}
}
結果是:
<message type="15" important="no">
<part>first</part>
<part>second</part>
<created>1387540760390</created>
</message>
我們有成功了!!!!
5,使用註解將字段轉換爲父節點文本內容
我們如果想得到的XML是如下形式:
<message type="15" important="no" created="1154097812245">This is the message content.</message>
就是將type、important、created三個節點全部變屬性,並且將content節點的內容變爲父節點message的內容,如何做?
這就需要用到
ToAttributedValueConverter轉換器註解
代碼如下:
// 新加的轉換器註解
@XStreamConverter(value = ToAttributedValueConverter.class, strings = { "content" })
// 對類的別名性註解
@XStreamAlias("message")
class RendezvousMessage {
// 將type節點變成屬性
@XStreamAsAttribute
// 對字段的別名性註解
@XStreamAlias("type")
private int messageType;
// 隱式集合性註解
@XStreamImplicit(itemFieldName = "part")
private List<String> content;
// 將important節點變成屬性
@XStreamAsAttribute
// 將true/false改爲yes/no
@XStreamConverter(value = BooleanConverter.class, booleans = { false }, strings = {
"yes", "no" })
private boolean important;
@XStreamConverter(SingleValueCalendarConverter.class)
private Calendar created = new GregorianCalendar();
public RendezvousMessage(int messageType, boolean important,
String... content) {
this.messageType = messageType;
this.important = important;
this.content = Arrays.asList(content);
}
}
但是運行之後,會發現,運行結果根本與我們預期的不一樣,爲什麼?
因爲ToAttributedValueConverter轉換器接受的content節點必須是String類型或者有一個轉換器將content裝換爲String類型!!!
例如,將content節點變爲String類型:
//新加的轉換註解
@XStreamConverter(value = ToAttributedValueConverter.class, strings = { "content" })
//對類的別名性註解
@XStreamAlias("message")
class RendezvousMessage {
// 對字段的別名性註解
@XStreamAlias("type")
private int messageType;
//由原來的List<String>類型變爲String類型
private String content;
// 將true/false改爲yes/no
@XStreamConverter(value = BooleanConverter.class, booleans = { false }, strings = {
"yes", "no" })
private boolean important;
// @XStreamConverter(SingleValueCalendarConverter.class)
// private Calendar created = new GregorianCalendar();
// 再次對構造方法進行了改造
public RendezvousMessage(int messageType, boolean important,
String content) {
this.messageType = messageType;
this.important = important;
this.content = content;
}
}
運行結果爲:
<message type="15" important="no">這是一大串content節點的內容</message>
雖然type和important節點沒有使用@XStreamAsAttribute註解,但是卻被隱式的轉換爲屬性。
6,使用註解忽略某些字段
忽略messageType字段可以使用@XStreamOmitField註解
代碼如下:
//對類的別名性註解
@XStreamAlias("message")
class RendezvousMessage {
//忽略messageType字段
@XStreamOmitField
// 將type節點變成屬性
@XStreamAsAttribute
// 對字段的別名性註解
@XStreamAlias("type")
private int messageType;
// 隱式集合性註解
@XStreamImplicit(itemFieldName = "part")
private List<String> content;
// 將important節點變成屬性
@XStreamAsAttribute
// 將true/false改爲yes/no
@XStreamConverter(value = BooleanConverter.class, booleans = { false }, strings = {
"yes", "no" })
private boolean important;
@XStreamConverter(SingleValueCalendarConverter.class)
private Calendar created = new GregorianCalendar();
// 再次對構造方法進行了改造
public RendezvousMessage(int messageType, boolean important,
String... content) {
this.messageType = messageType;
this.important = important;
this.content = Arrays.asList(content);
}
}
運行結果:
<message important="no">
<part>first</part>
<part>second</part>
<created>1387544212500</created>
</message>
7,自動檢測註解
之前我們啓用某個類的註解時,都需要使用processAnnotations方法通知xstream對象解析註解類,其實我們還有一個更簡便的模式,即調用autodetectAnnotations(true)方法,讓xstream對象自動檢測註解類:
public class XStreamTest3 {
public static void main(String[] args) {
XStream stream = new XStream();
// // 通知XStream對象讀取並識別RendezvousMessage中的註解
// stream.processAnnotations(RendezvousMessage.class);
//自動檢測註解
stream.autodetectAnnotations(true);
RendezvousMessage msg = new RendezvousMessage(15, false, "first","second");
System.out.println(stream.toXML(msg));
}
}
注意:1,自動檢測註解模式,會使XStream的解析變慢!2,在任何地方調用processAnnotations方法之後,自動檢測註解模式將會被關閉。