六、使用注解(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方法之后,自动检测注解模式将会被关闭。
?
?
?
?
?
?
?
?
?