XStream

XStream is a simple library to serialize objects to XML and back again.

Features

  • Ease of use. A high level facade is supplied that simplifies common use cases.

  • No mappings required. Most objects can be serialized without need for specifying mappings.

  • Performance. Speed and low memory footprint are a crucial part of the design, making it suitable for large object graphs or systems with high message throughput.

  • Clean XML. No information is duplicated that can be obtained via reflection. This results in XML that is easier to read for humans and more compact than native Java serialization.

  • Requires no modifications to objects. Serializes internal fields, including private and final. Supports non-public and inner classes. Classes are not required to have default constructor.

  • Full object graph support. Duplicate references encountered in the object-model will be maintained. Supports circular references.

  • Integrates with other XML APIs. By implementing an interface, XStream can serialize directly to/from any tree structure (not just XML).

  • Customizable conversion strategies. Strategies can be registered allowing customization of how particular types are represented as XML.

  • Security framework. Fine-control about the unmarshalled types to prevent security issues with manipulated input.

  • Error messages. When an exception occurs due to malformed XML, detailed diagnostics are provided to help isolate and fix the problem.

  • Alternative output format. The modular design allows other output formats. XStream ships currently with JSON support and morphing.

  • Typical Uses

Hello World

Title: XStream Demo Object->Xml: Trans object into xml; Xml->Obejct: Trans xml into xml;

(这个官方的例子稍微有些不够简洁,但是也很容易理解。效果显著)

使用之前,引入Jar

<dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.4.9</version>
</dependency>

Obj2Xml

  • toXmlTest()
@Test
public void toXmlTest() {
     XStream xstream = new XStream(new StaxDriver());
     xstream.alias("person", Person.class);
     xstream.alias("phoneNum", PhoneNum.class);

     Person joe = new Person();
     joe.setFirstname("hello");
     joe.setLastname("world");
     joe.setPhone(new PhoneNum(123, "1234-456"));
     joe.setFax(new PhoneNum(123, "9999-999"));

     String xml = xstream.toXML(joe);
     System.out.println(xml);
}

其中实体类如下(下一个列子也是用此实体类):

  • Person.java
@Data
public class Person {

    private String firstname;

    private String lastname;

    private PhoneNum phone;

    private PhoneNum fax;

}
  • PhoneNum.java
@Data
public class PhoneNum {
    private int code;
    private String number;

    public PhoneNum(int code, String number) {
        this.code = code;
        this.number = number;
    }
}

为了简单,此处不适用日志。直接打印。结果如下:

<?xml version="1.0" ?><person><firstname>hello</firstname><lastname>world</lastname><phone><code>123</code><number>1234-456</number></phone><fax><code>123</code><number>9999-999</number></fax></person>

Xml2Obj

既然可以将对象转成XML,反之肯定也可以。如下:

  • toObjectTest()
@Test
public void toObjectTest() {
     final String xml = "<?xml version=\"1.0\" ?><person><firstname>hello</firstname><lastname>world</lastname><phone><code>123</code><number>1234-456</number></phone><fax><code>123</code><number>9999-999</number></fax></person>";
     XStream xstream = new XStream(new StaxDriver());
     xstream.alias("person", Person.class);
     xstream.alias("phoneNum", PhoneNum.class);
     Person newJoe = (Person)xstream.fromXML(xml);
     System.out.println(newJoe.getFirstname());  
}

打印结果:

hello

Alias

有时候我们为了更便于阅读或者简化会为一样东西提供一个别名。我们来看一个简单的例子。

假设我们要生成如图的xml文件:

<blog author="ryo">
  <entry>
    <title>first</title>
    <description>My first blog entry.</description>
  </entry>
  <entry>
    <title>tutorial</title>
    <description>
        Today we have developed a nice alias tutorial. Tell your friends! NOW!
    </description>
  </entry>
</blog>

首先,我们会建立对应实体类。

  • Blog.java
@Data
public class Blog {
    private Author writer;
    private List<Entry> entries = new LinkedList<>();
}
  • Author.java
@Data
public class Author {

    private String name;

    public Author(String name) {
        this.name = name;
    }
}
  • Entry.java
@Data
public class Entry {

    private String title;

    private String description;

    public Entry(String title, String description) {
        this.title = title;
        this.description = description;
    }
}

然后,我们写一个简单的测试。

  • initTest()
@Test
public void initTest() {
    Author author = new Author("ryo");
    Blog teamBlog = new Blog();
    teamBlog.setWriter(author);

    List<Entry> entries = new LinkedList<>();
    entries.add(new Entry("first","My first blog entry."));
    entries.add(new Entry("tutorial",
            "Today we have developed a nice alias tutorial. Tell your friends! NOW!"));
    teamBlog.setEntries(entries);

    XStream xstream = new XStream();
    System.out.println(xstream.toXML(teamBlog));
}

打印结果:

<com.ryo.convert.test.domain.Blog>
  <writer>
    <name>ryo</name>
  </writer>
  <entries class="linked-list">
    <com.ryo.convert.test.domain.Entry>
      <title>first</title>
      <description>My first blog entry.</description>
    </com.ryo.convert.test.domain.Entry>
    <com.ryo.convert.test.domain.Entry>
      <title>tutorial</title>
      <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
    </com.ryo.convert.test.domain.Entry>
  </entries>
</com.ryo.convert.test.domain.Blog>

一、Class Alias

我们将XStream调用时简单修改如下:(这个在一开始的例子中其实已经使用过了)

XStream xstream = new XStream();
xstream.alias("blog", Blog.class);
xstream.alias("entry", Entry.class);

输出结果如下:

<blog>
  <writer>
    <name>ryo</name>
  </writer>
  <entries class="linked-list">
    <entry>
      <title>first</title>
      <description>My first blog entry.</description>
    </entry>
    <entry>
      <title>tutorial</title>
      <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
    </entry>
  </entries>
</blog>
  • Field Alias

上面的输出看起来简洁了很多,但是我们想把 writer 改为 author 怎么做呢?接着看。

只需要添加一句话:

xstream.aliasField("author", Blog.class, "writer");

结果:

<blog>
  <author>
    <name>ryo</name>
  </author>
  <entries class="linked-list">
    <entry>
      <title>first</title>
      <description>My first blog entry.</description>
    </entry>
    <entry>
      <title>tutorial</title>
      <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
    </entry>
  </entries>
</blog>

三、Implicit collection

如果我们有一个集合,但是我们不想把根节点显示出来。比如本例子,不想显示 entries 节点。

xstream.addImplicitCollection(Blog.class, "entries");

结果如下:

<blog>
  <author>
    <name>ryo</name>
  </author>
  <entry>
    <title>first</title>
    <description>My first blog entry.</description>
  </entry>
  <entry>
    <title>tutorial</title>
    <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
  </entry>
</blog>

四、Attribute Alias

我们离最后的目标还差一点。如何将 author 属性变为 XML 的属性呢。

  • AttributeAliasTest()
XStream xstream = new XStream();
xstream.alias("blog", Blog.class);
xstream.alias("entry", Entry.class);
xstream.addImplicitCollection(Blog.class, "entries");

xstream.useAttributeFor(Blog.class, "writer");
xstream.aliasField("author", Blog.class, "writer");
xstream.registerConverter(new AuthorConverter());

System.out.println(xstream.toXML(teamBlog));
  • AuthorConverter.java

我们必须告诉 XStream 如何将字段属性转换为 XML 的 tag 属性。

public class AuthorConverter implements SingleValueConverter {

    @Override
    public String toString(Object obj) {
        return ((Author) obj).getName();
    }

    @Override
    public Object fromString(String name) {
        return new Author(name);
    }

    @Override
    public boolean canConvert(Class type) {
        return type.equals(Author.class);
    }

}

输出结果如下:

<blog author="ryo">
  <entry>
    <title>first</title>
    <description>My first blog entry.</description>
  </entry>
  <entry>
    <title>tutorial</title>
    <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
  </entry>
</blog>

大功告成!

五、Package Alias

我们再看一个可能会用到的技能——为包名起一个别名。

XStream xstream = new XStream();
xstream.aliasPackage("my.company", "com.ryo.convert.test.domain");
System.out.println(xstream.toXML(teamBlog));

结果如下:

<my.company.Blog>
  <writer>
    <name>ryo</name>
  </writer>
  <entries class="linked-list">
    <my.company.Entry>
      <title>first</title>
      <description>My first blog entry.</description>
    </my.company.Entry>
    <my.company.Entry>
      <title>tutorial</title>
      <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
    </my.company.Entry>
  </entries>
</my.company.Blog>

Annotation

使用注解可以使得编程更加方便。

一、创始之初

  • Author.java
@Data
public class Author {

    private String name;

    public Author(String name) {
        this.name = name;
    }
}

直接输出XML

@Test
public void initTest() {
    Author author = new Author("ryo");
    XStream xStream = new XStream();
    String xml = xStream.toXML(author);
    System.out.println(xml);
}

输出结果如下:

<com.ryo.convert.test.domain.Author>
  <name>ryo</name>
</com.ryo.convert.test.domain.Author>

二、别名

  • Author.java
@Data
@XStreamAlias("author")
public class Author {

    @XStreamAlias("writer")
    private String name;

    public Author(String name) {
        this.name = name;
    }
}
  • 执行注解
@Test
public void aliasTest() {
    Author author = new Author("ryo");
    XStream xStream = new XStream();
    xStream.processAnnotations(Author.class);
    String xml = xStream.toXML(author);
    System.out.println(xml);
}

执行注解的方式,除却

xStream.processAnnotations(Author.class);

还可以使用

xstream.autodetectAnnotations(true);

结果如下:

<author>
  <writer>ryo</writer>
</author>

三、Implicit Collection

  • Author.java
@Data
@XStreamAlias("author")
public class Author {

    @XStreamAlias("writer")
    private String name;

    @XStreamImplicit(itemFieldName = "hobby")
    private List<String> hobby;

    public Author(String name) {
        this.name = name;
    }
}
  • 测试
@Test
public void collectionImplicitTest() {
    Author author = new Author("ryo");
    List<String> hobby = Arrays.asList("fly", "swim");
    author.setHobby(hobby);

    XStream xStream = new XStream();
    xStream.processAnnotations(Author.class);
    String xml = xStream.toXML(author);
    System.out.println(xml);
}

结果

<author>
  <writer>ryo</writer>
  <hobby>fly</hobby>
  <hobby>swim</hobby>
</author>

四、定义转换

  • Author.java
@Data
@XStreamAlias("author")
public class Author {

    @XStreamAlias("writer")
    private String name;

    @XStreamImplicit(itemFieldName = "hobby")
    private List<String> hobby;
    
    @XStreamConverter(value=BooleanConverter.class, booleans={false}, strings={"yes", "no"})
    private boolean isImportant;
    
    @XStreamConverter(SimpleCalendarConveter.class)
    private Calendar created = new GregorianCalendar();

    public Author(String name) {
        this.name = name;
    }
}

其中 SimpleCalendarConveter.java 如下:

public class SimpleCalendarConveter implements Converter {

    @Override
    public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) {
        Calendar calendar = (Calendar) o;
        hierarchicalStreamWriter.setValue(String.valueOf(calendar.getTime().getTime()));
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) {
        GregorianCalendar calendar = new GregorianCalendar();
        calendar.setTime(new Date(Long.parseLong(hierarchicalStreamReader.getValue())));
        return calendar;
    }

    @Override
    public boolean canConvert(Class aClass) {
        return aClass.equals(GregorianCalendar.class);
    }
}

执行结果如下:

<author>
  <writer>ryo</writer>
  <hobby>fly</hobby>
  <hobby>swim</hobby>
  <isImportant>no</isImportant>
  <created>1497968044970</created>
</author>

四、 Attributes

通过注解 @XStreamAsAttribute 即可。如下:

  • Author.java
@Data
@XStreamAlias("author")
public class Author {

    @XStreamAlias("writer")
    @XStreamAsAttribute
    private String name;

    @XStreamImplicit(itemFieldName = "hobby")
    private List<String> hobby;

    public Author(String name) {
        this.name = name;
    }
}

测试结果:

<author writer="ryo">
  <hobby>fly</hobby>
  <hobby>swim</hobby>
</author>

五、Omitting Fields

可以通过 @XStreamOmitField 不序列化某个字段。

  • Author.java
@Data
@XStreamAlias("author")
public class Author {

    @XStreamAlias("writer")
    @XStreamOmitField
    private String name;

    @XStreamImplicit(itemFieldName = "hobby")
    private List<String> hobby;

    public Author(String name) {
        this.name = name;
    }
}

结果如下:

<author>
  <hobby>fly</hobby>
  <hobby>swim</hobby>
</author>

Converter

一、 Simple Converter

@Test
public void simpleConverterTest() {
    Person person = new Person();
    person.setName("ryo");

    XStream xStream = new XStream();
    xStream.processAnnotations(Person.class);
    String xml = xStream.toXML(person);
    System.out.println(xml);
}

其中 Person.java 内容如下:

@Data
@XStreamAlias("Person")
public class Person {

    private String name;

}

输出结果如下:

<Person>
  <name>ryo</name>
</Person>

二、String representation

@Test
public void withToStrConverterTest() {
    Person person = new Person();
    person.setName("ryo");

    XStream xStream = new XStream(new DomDriver());
    xStream.registerConverter(new PersonToStrConverter());
    xStream.alias("person", Person.class);
    System.out.println(xStream.toXML(person));
}

其中 PersonToStrConverter 内容为:

public class PersonToStrConverter extends AbstractSingleValueConverter {

    @Override
    public boolean canConvert(Class aClass) {
        return aClass.equals(Person.class);
    }

    @Override
    public Object fromString(String s) {
        Person person = new Person();
        person.setName(s);
        return person;
    }
}

输出结果为:

<person>Person(name=ryo)</person>

三、DateConverter

@Test
public void dateConverterTest() {
    // grabs the current date from the virtual machine
    Calendar calendar = new GregorianCalendar();

    // creates the xstream
    XStream xStream = new XStream(new DomDriver());

    // brazilian portuguese locale
    xStream.registerConverter(new DateConverter(new Locale("pt", "br")));

    // prints the result
    System.out.println(xStream.toXML(calendar));

}

其中 DateConverter.java

public class DateConverter implements Converter {

    private Locale locale;

    public DateConverter(Locale locale) {
        super();
        this.locale = locale;
    }

    @Override
    public boolean canConvert(Class clazz) {
        return Calendar.class.isAssignableFrom(clazz);
    }

    @Override
    public void marshal(Object value, HierarchicalStreamWriter writer,
                        MarshallingContext context) {
        Calendar calendar = (Calendar) value;
        Date date = calendar.getTime();
        DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
                this.locale);
        writer.setValue(formatter.format(date));
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader,
                            UnmarshallingContext context) {
        GregorianCalendar calendar = new GregorianCalendar();
        DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
                this.locale);
        try {
            calendar.setTime(formatter.parse(reader.getValue()));
        } catch (ParseException e) {
            throw new ConversionException(e.getMessage(), e);
        }
        return calendar;
    }

}

打印结果:

<gregorian-calendar>Domingo, 25 de Junho de 2017</gregorian-calendar>

对于xml转为对象:

@Test
public void dateConverter2ObjTest() {
    // grabs the current date from the virtual machine
    Calendar calendar = new GregorianCalendar();

    // creates the xstream
    XStream xStream = new XStream(new DomDriver());

    // brazilian portuguese locale
    xStream.registerConverter(new DateConverter(new Locale("pt", "br")));


    // loads the calendar from the string
    Calendar loaded = (Calendar) xStream
            .fromXML("<gregorian-calendar>Sexta-feira, 10 de Fevereiro de 2006</gregorian-calendar>");
    // prints using the system defined locale
    System.out.println(DateFormat.getDateInstance(DateFormat.SHORT).format(
            loaded.getTime()));
}

输出结果如下:

06-2-10

四、Complex Converter

public class BirthdayConverter implements Converter {

    @Override
    public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) {
        Birthday birthday = (Birthday)o;
        if (birthday.getGender() != '\0') {
            hierarchicalStreamWriter.addAttribute("gender", Character.toString(birthday.getGender()));
        }
        if (birthday.getPerson() != null) {
            hierarchicalStreamWriter.startNode("person");
            marshallingContext.convertAnother(birthday.getPerson());
            hierarchicalStreamWriter.endNode();
        }
        if (birthday.getDate() != null) {
            hierarchicalStreamWriter.startNode("birth");
            marshallingContext.convertAnother(birthday.getDate());
            hierarchicalStreamWriter.endNode();
        }
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) {
        Birthday birthday = new Birthday();
        String gender = hierarchicalStreamReader.getAttribute("gender");
        if (gender != null) {
            if (gender.length() > 0) {
                if (gender.charAt(0) == 'f') {
                    birthday.setGenderFemale();
                } else if (gender.charAt(0) == 'm') {
                    birthday.setGenderMale();
                } else {
                    throw new ConversionException("Invalid gender value: " + gender);
                }
            } else {
                throw new ConversionException("Empty string is invalid gender value");
            }
        }
        while (hierarchicalStreamReader.hasMoreChildren()) {
            hierarchicalStreamReader.moveDown();
            if ("person".equals(hierarchicalStreamReader.getNodeName())) {
                Person person = (Person)unmarshallingContext.convertAnother(birthday, Person.class);
                birthday.setPerson(person);
            } else if ("birth".equals(hierarchicalStreamReader.getNodeName())) {
                Calendar date = (Calendar)unmarshallingContext.convertAnother(birthday, Calendar.class);
                birthday.setDate(date);
            }
            hierarchicalStreamReader.moveUp();
        }
        return birthday;
    }

    @Override
    public boolean canConvert(Class aClass) {
        return Birthday.class.equals(aClass);
    }
}

其中 Birthday.java

@Data
public class Birthday {

    private Person person;

    private Calendar date;

    private char gender;

    public void setGenderMale() {
        this.gender = 'm';
    }

    public void setGenderFemale() {
        this.gender = 'f';
    }
}

Object Streams

XStream provides alternative implementations of java.io.ObjectInputStream and java.io.ObjectOutputStream, allowing streams of objects to be serialized or deserialized from XML.

This is useful when processing large sets of objects, as only one needs to be in memory at a time.

Obviously you should use also a stream-based XML parser reading the XML. A DOM-based XML parser will process the complete XML and build the object model before XStream is able to to handle the first element.

Object Streams BLOG

ObjectOutputStream

  • ObjectOutputStreamTest()
/**
 * 对象输出流
 *
 * @throws IOException
 * @since 1.7
 */
@Test
public void ObjectOutputStreamTest() throws IOException {
    XStream xstream = new XStream();

    try (ObjectOutputStream oos = xstream.createObjectOutputStream(System.out, "root")) {
        oos.writeObject(new Person("张三"));
        oos.writeObject(new Person("李四"));
        oos.writeObject(1);
        oos.writeObject(2);
        oos.writeObject(3d);
        oos.writeObject(4d);
        oos.writeObject('c');
        oos.writeObject("这是一堆字符串!");
    }
}
  • Person.java
@Data
public class Person {

    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }
}

输出结果为:

<root>
    <com.ryo.convert.test.converter.domain.Person>
        <name>张三</name>
    </com.ryo.convert.test.converter.domain.Person>
    <com.ryo.convert.test.converter.domain.Person>
        <name>李四</name>
    </com.ryo.convert.test.converter.domain.Person>
    <int>1</int>
    <int>2</int>
    <double>3.0</double>
    <double>4.0</double>
    <char>c</char>
    <string>这是一堆字符串!</string>
</root>

ObjectInputStream

  • ObjectInputStreamTest()
/**
 * 文件输入流
 */
@Test
public void ObjectInputStreamTest() throws IOException, ClassNotFoundException {
    XStream xstream = new XStream();
    final String filePath = "D:\\CODE\\converter\\converter-test\\src\\test\\resources\\test.xml";
    FileReader reader2 = new FileReader(new File(filePath));
    ObjectInputStream ois = xstream.createObjectInputStream(reader2);

    Person p1 = (Person)ois.readObject();
    System.out.println("p1="+p1);

    Person p2 = (Person)ois.readObject();
    System.out.println("p2="+p2);

    int i1 = (Integer)ois.readObject();
    System.out.println("i1="+i1);

    int i2 = (Integer)ois.readObject();
    System.out.println("i2="+i2);

    double d1 = (Double)ois.readObject();
    System.out.println("d1="+d1);

    double d2 = (Double)ois.readObject();
    System.out.println("d2="+d2);

    char ch = (Character)ois.readObject();
    System.out.println("ch="+ch);

    String str = (String)ois.readObject();
    System.out.println("str="+str);

    System.out.println("******异常捕获******");
    //发生异常
    try {
        ois.readObject();
    } catch (EOFException e) {
        System.out.println("因为已经没有数据了,再次读取时,就会发生EOFException异常");
    }
}

其中,filePath对应的 test.xml 内容为:

<root>
    <com.ryo.convert.test.converter.domain.Person>
        <name>张三</name>
    </com.ryo.convert.test.converter.domain.Person>
    <com.ryo.convert.test.converter.domain.Person>
        <name>李四</name>
    </com.ryo.convert.test.converter.domain.Person>
    <int>1</int>
    <int>2</int>
    <double>3.0</double>
    <double>4.0</double>
    <char>c</char>
    <string>这是一堆字符串!</string>
</root>

输出结果为:

p1=Person(name=张三)
p2=Person(name=李四)
i1=1
i2=2
d1=3.0
d2=4.0
ch=c
str=这是一堆字符串!
******异常捕获******
因为已经没有数据了,再次读取时,就会发生EOFException异常

Persist API

Adding elements

  • addElemTest()
@Test
public void addElemTest() {
    final String dirPath = "D:\\CODE\\converter\\converter-test\\src\\test\\resources\\persist\\";
    // prepares the file strategy to directory /tmp
    PersistenceStrategy strategy = new FilePersistenceStrategy(new File(dirPath));
    // creates the list:
    List list = new XmlArrayList(strategy);

    // adds four authors
    list.add(new Person("joe walnes"));
    list.add(new Person("joerg schaible"));
    list.add(new Person("mauro talevi"));
    list.add(new Person("guilherme silveira"));

    // adding an extra author
    Person mistake = new Person("mama");
    list.add(mistake);
}

运行完程序之后,可以在对应文件夹下看到如下文件:

2017/07/22  14:42               120 int@0.xml
2017/07/22  14:42               124 int@1.xml
2017/07/22  14:42               122 int@2.xml
2017/07/22  14:42               128 int@3.xml
2017/07/22  14:42               114 int@4.xml

Local Converter

此处暂时跳过。

JSON

Jettison driver

@Test
public void JettisonTest() {
    Person person = new Person("ryo");

    XStream xstream = new XStream(new JettisonMappedXmlDriver());
    xstream.setMode(XStream.NO_REFERENCES);
    xstream.alias("person", Person.class);

    System.out.println(xstream.toXML(person));
}

此处测试时需要引入对应jar。个人觉得没有必要,json可以使用很成熟的三方jar。

暂时跳过。