Академический Документы
Профессиональный Документы
Культура Документы
Xstream
Author: Angelin
Introduction
XStream is a lightweight and easy-to-use open source Java™ library
used for
serializing objects into XML and deserializing XML back into objects. It uses
reflection API to do this.
XStream is a serialization tool and not a data binding tool, which
means that it
does not generate a Java source code file from a DTD or XML Schema
Definition
(XSD) file. The Java classes that are to be serialized needs to be written
such that its
fields would map to the elements of the XML that you wish to read. Even
though
XStream can be defined as a serialization library, the classes that you
want to
serialize need not implement the Serializable marker interface.
Xstream uses a converter-based design for converting certain
known types
such as primitives, String, File, Collections, arrays, and Dates, to and from
XML. For
other objects, reflection is used to serialize each field recursively. Xstream
can
serialize object graphs which are nested.
Initializing Xstream
To use XStream, simply instantiate the XStream class:
XStream xstream = new XStream();
You require xstream-[version].jar and xpp3-[version].jar in the classpath.
XPP3 is a
very fast XML pull-parser implementation. If you do not want to include
this
dependency, you can use a standard JAXP DOM parser instead:
XStream xstream = new XStream(new DomDriver());
// does not require XPP3 library
Output
<string>Hello, World!</string>
The program uses XStream to output a string in XML. The public class
HelloWorld
imports only one class, com.thoughtworks.xstream.XStream. It calls a
constructor for
XStream, creating the object xstream. Then it uses the toXML method to
store the
string salutation as XML, and writes the XML to the console using the
System.out.print method.
XStream automatically wraps the Java String salutation in the XML
element string.
The string element appears to be an example of XStream's style of XML
reflection.
Person.java
package com.example;
public class Person {
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
private String name;
private int age;
private PhoneNumber phone;
private PhoneNumber fax;
public PhoneNumber getPhone() {
return phone;
}
public void setPhone(PhoneNumber phone) {
this.phone = phone;
}
public PhoneNumber getFax() {
return fax;
}
public void setFax(PhoneNumber fax) {
this.fax = fax;
}
public String toString() {
return "\nName: " + name + "\nAge: " + age + "\nPhone:" + phone
+ "\nFax:" + fax;
}
}
PhoneNumber.java
package com.example;
public class PhoneNumber {
private int code;
private String number;
public PhoneNumber(int code, String number) {
super();
this.code = code;
this.number = number;
}
public String toString() {
return code + "-" + number;
}
}
Output
<com.example.Person>
<name>Joe</name>
<age>23</age>
<phone>
<code>123</code>
<number>123456</number>
</phone>
<fax>
<code>123</code>
<number>112233</number>
</fax>
</com.example.Person>
Name: Joe
Age: 23
Phone:123-123456
Fax:123-112233
Class aliasing
When Xstream generates xml element corresponding to the class, it by
default uses the fully qualified class name for the element name, including
the
package name.For example, <com.example.Person>. If you need only the
class name (without the package name) as the XML element name, you
have
to use XStream Aliases.
Field aliasing
To create an alias for any field (i.e., class member), use
xstream.aliasField(String alias, Class definedIn, String fieldName);
For e.g. adding the line,
xstream.aliasField("Name", Person.class, "name");
produces the following XML:
<Person>
<Name>Joe</Name>
<age>23</age>
<phone>
<code>123</code>
<number>123456</number>
</phone>
<fax>
<code>123</code>
<number>112233</number>
</fax>
</Person>
Attribute aliasing
A primitive field can be made as an attribute instead of a child element,
using
the useAttributeFor() methods. The method signature of one of the
useAttributeFor() methods is given below:
xstream.useAttributeFor(Class definedIn, String fieldName);
Then, using the aliasField() method an alias can be defined for this
attribute.
For e.g. adding the lines,
xstream.useAttributeFor(PhoneNumber.class, "code");
xstream.aliasField("AreaCode", PhoneNumber.class, "code");
xstream.useAttributeFor(PhoneNumber.class, "number");
xstream.aliasField("Number", PhoneNumber.class, "number");
makes the code and number fields of the PhoneNumber class appear as
attributes
with new alias names 'AreaCode' and 'Number' respectively and produces
the
following XML:
<Person>
<Name>Joe</Name>
<age>23</age>
<phone AreaCode="123" Number="123456"/>
<fax AreaCode="123" Number="112233"/>
</Person>
Alternately, aliasAttribute() method can be used to define a field to appear
as
an attribute and give an alias name for it. The method signature of one of
the
useAttributeFor() methods is given below:
To demonstrate this, let us add one more class to hold the details of the
Company to which the Person belongs and make this Company
information
appear as an attribute of the Person tag.
PhoneNumber.java
package com.example;
public class PhoneNumber {
private int code;
private String number;
public PhoneNumber(int code, String number) {
super();
this.code = code;
this.number = number;
}
}
Company.java
package com.example;
public class Company {
private String name;
public Company(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
}
CompanyConverter.java
package com.example;
import com.thoughtworks.xstream.converters.SingleValueConverter;
/**
* Single value converter, responsible for converting String to Company
and vice
* versa
*
*/
public class CompanyConverter implements SingleValueConverter {
/**
* This method is used to extract a String from the given Object
*/
public String toString(Object obj) {
return ((Company) obj).getName();
}
/**
* This method takes a String and returns an Object
*/
public Object fromString(String name) {
return new Company(name);
}
/**
* This method tells XStream which types it can deal with
*/
public boolean canConvert(Class type) {
return type.equals(Company.class);
}
}
TestXstream.java
package com.example;
import com.thoughtworks.xstream.XStream;
public class TestXstream {
public static void main(String[] args) {
XStream xstream = new XStream();
Person joe = new Person("Joe", 23);
joe.setPhone(new PhoneNumber(123,"123456"));
joe.setFax(new PhoneNumber(123,"112233"));
joe.setCompany(new Company("XYZ"));
xstream.alias("Person", Person.class);
xstream.aliasAttribute(Person.class, "company", "Company");
//Register the converter
xstream.registerConverter(new CompanyConverter());
//Convert a Java object to XML
String xml = xstream.toXML(joe);
System.out.println(xml);
}
}
Output
<Person Company="XYZ">
<name>Joe</name>
<age>23</age>
<phone>
<code>123</code>
<number>123456</number>
</phone>
<fax>
<code>123</code>
<number>112233</number>
</fax>
</Person>
Package aliasing
Package aliases can be used to change the package name that
appears in the element name of the generated XML for the class name.
Customers.java
package com.example;
import java.util.List;
public class Customers {
private List customers;
/**
* @param customers the customers to set
*/
public void setCustomers(List customers) {
this.customers = customers;
}
}
TestXstream.java
package com.example;
import java.util.ArrayList;
import com.thoughtworks.xstream.XStream;
public class TestXstream {
public static void main(String[] args) {
XStream xstream = new XStream();
ArrayList persons = new ArrayList();
Person joe = new Person("Joe", 23);
joe.setPhone(new PhoneNumber(123, "123456"));
joe.setFax(new PhoneNumber(123, "112233"));
joe.setCompany(new Company("XYZ"));
Person jack = new Person("Jack", 23);
jack.setPhone(new PhoneNumber(321, "654321"));
jack.setFax(new PhoneNumber(321, "111111"));
jack.setCompany(new Company("ABC"));
persons.add(joe);
persons.add(jack);
Customers customers = new Customers();
customers.setCustomers(persons);
xstream.alias("Person", Person.class);
xstream.aliasAttribute(Person.class, "company", "Company");
//Register the converter
xstream.registerConverter(new CompanyConverter());
//Omit the phone and fax field information from the resulting XML
xstream.omitField(Person.class, "phone");
xstream.omitField(Person.class, "fax");
//Set alias name for Customers class
xstream.alias("Customers", Customers.class);
//Convert a Java object to XML
String xml = xstream.toXML(customers);
System.out.println(xml);
//Omit the container element representing the list of customers
xstream.addImplicitCollection(Customers.class, "customers");
//Convert a Java object to XML
xml = xstream.toXML(customers);
System.out.println("\nAfter omitting the container tag of the
collection:");
System.out.println(xml);
}
}
Output
<Customers>
<customers>
<Person Company="XYZ">
<name>Joe</name>
<age>23</age>
</Person>
<Person Company="ABC">
<name>Jack</name>
<age>23</age>
</Person>
</customers>
</Customers>
Annotations
Annotations simplifies the process of setting aliases and registering
converters etc.
The table below shows the different annotation types supported by
Xstream.
Annotation Types Description
XStreamAlias Annotation used to define an XStream class or field value.
XStreamAsAttribute Defines that a field should be serialized as an
attribute.
XStreamConverter Annotation to declare a converter.
XStreamImplicit An annotation for marking a field as an implicit
collection.
XStreamInclude Annotation to force automated processing of further
classes.
XStreamOmitField Declares a field to be omitted.
Now let us see how we can use annotations instead of the Xstream
methods that we
had used in the sample codes in the previous section on tweaking output
using alias
names
• @XstreamAlias
Instead of using the following statement to set an alias name for the class
Person:
xstream.alias("Person", Person.class);
the @XStreamAlias annotation can be written in the class Person as shown
below:
@XStreamAlias("Person")
public class Person {
...
...
}
@XStreamAlias("Customers")
public class Customers {
...
...
}
• @XstreamAsAttribute
Instead of using the following statement to make the member variable
company defined in the Person class appear as an attribute of the Person
tag
and creating an alias for it:
xstream.aliasAttribute(Person.class, "company", "Company");
the @XStreamAlias annotations can be written in the class Person as
shown
below:
@XStreamAlias("Person")
public class Person {
...
...
@XStreamAsAttribute
@XStreamAlias("Company")
private Company company;
...
...
}
• @XStreamConverter
Instead of using the following statement to register a converter for the
class
Company:
xstream.registerConverter(new CompanyConverter());
the @XStreamConverter annotation can be written in the class Company
as
shown below:
@XStreamConverter(CompanyConverter.class)
public class Company {
...
...
}
To register the custom converter locally, i.e. only for the member variable
company defined in the Person class, the @XStreamConverter annotation
should be written in the class Person as shown below:
@XStreamAlias("Person")
public class Person {
...
... @XStreamAsAttribute
@XstreamAlias("Company")
@XStreamConverter(CompanyConverter.class)
private Company company;
...
...
}
• @XStreamOmitField
Instead of using the following statements to omit fields from the
serialization
process:
xstream.omitField(Person.class, "phone");
xstream.omitField(Person.class, "fax");
the @XStreamOmitField annotation can be written in the class Person as
shown below:
@XStreamAlias("Person")
public class Person {
...
@XStreamOmitField
private PhoneNumber phone;
@XStreamOmitField
private PhoneNumber fax;
...
}
• @XStreamImplicit
Instead of using the following statement to omit the container element
representing the list of customers:
xstream.addImplicitCollection(Customers.class, "customers");
the @XStreamImplicit annotation can be written in the class Customers as
shown below:
@XStreamAlias("Customers")
public class Customers {
@XStreamImplicit
private List customers;
...
...
}
The code of the Person class, Company class and the Customers class,
updated
with annotations is given below.
Person.java
package com.example;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
@XStreamAlias("Person")
public class Person {
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
Company.java
package com.example;
@XStreamConverter(CompanyConverter.class)
public class Company {
private String name;
public Company(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
}
Customers.java
package com.example;
import java.util.List;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
@XStreamAlias("Customers")
public class Customers {
@XStreamImplicit
private List customers;
/**
* @param customers
* the customers to set
*/
public void setCustomers(List customers) {
this.customers = customers;
}
}
package com.example;
import java.util.ArrayList;
import com.thoughtworks.xstream.XStream;
public class TestXstream {
public static void main(String[] args) {
XStream xstream = new XStream();
ArrayList persons = new ArrayList();
Person joe = new Person("Joe", 23);
joe.setPhone(new PhoneNumber(123, "123456"));
joe.setFax(new PhoneNumber(123, "112233"));
joe.setCompany(new Company("XYZ"));
Person jack = new Person("Jack", 23);
jack.setPhone(new PhoneNumber(321, "654321"));
jack.setFax(new PhoneNumber(321, "111111"));
jack.setCompany(new Company("ABC"));
persons.add(joe);
persons.add(jack);
Output
Serialized XML generated when the annotations were not processed:
<com.example.Customers>
<customers>
<com.example.Person>
<name>Joe</name>
<age>23</age>
<company>
<name>XYZ</name>
</company>
<phone>
<code>123</code>
<number>123456</number>
</phone>
<fax>
<code>123</code>
<number>112233</number>
</fax>
</com.example.Person>
<com.example.Person>
<name>Jack</name>
<age>23</age>
<company>
<name>ABC</name>
</company>
<phone>
<code>321</code>
<number>654321</number>
</phone>
<fax>
<code>321</code>
<number>111111</number>
</fax>
</com.example.Person>
</customers>
</com.example.Customers>
Serialized XML generated after processing the annotations:
<Customers>
<Person Company="XYZ">
<name>Joe</name>
<age>23</age>
</Person>
<Person Company="ABC">
<name>Jack</name>
<age>23</age>
</Person>
</Customers>
Converters
The responsibility of a Converter is to provide a strategy for
converting
particular types of objects found in the object graph, to and from XML.
XStream
provides Converters for common types such as primitives, String, File,
Collections,
arrays, and Dates.
Here, XStream converts the date value into String and automatically
wraps
the Date object into its XML element representation <date>.
Custom converters
Custom converters can be written to customize the information being
serialised or
deserialised. They can be implemented and registered using the
XStream.registerConverter() method.
Converters for objects that can store all information in a single value
should
implement SingleValueConverter.
This can be understood using the folowing example. Let us modify the
Company
class and its custom converter class CompanyConverter that were earlier
given in
the section 'Omitting fields and root tag of collection'.
Company.java
package com.example;
public class Company {
private String name;
private String address;
public Company(String name) {
super();
this.name = name;
}
public Company(String name, String address) {
super();
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
}
CompanyConverter.java
package com.example;
import com.thoughtworks.xstream.converters.SingleValueConverter;
/**
* Single value converter, responsible for converting String to Company
and vice
* versa
*
*/
public class CompanyConverter implements SingleValueConverter {
/**
* This method is used to extract a String from the given Object
*/
public String toString(Object obj) {
return ((Company) obj).getName() + " located at "
+ ((Company) obj).getAddress();
}
/**
* This method takes a String and returns an Object
*/
public Object fromString(String name) {
return new Company(name);
}
/**
* This method tells XStream which types it can deal with
*/
public boolean canConvert(Class type) {
return type.equals(Company.class);
}
}
ConverterExample.java
package com.example;
import com.thoughtworks.xstream.XStream;
public class ConverterExample {
public static void main(String[] args) {
XStream xstream = new XStream();
Company company = new Company("XYZ");
xstream.alias("Company", Company.class);
//Without using the CompanyConverter
String xml = xstream.toXML(company);
System.out.println("Without using the CompanyConverter:\n" + xml);
//Using the CompanyConverter
xstream.registerConverter(new CompanyConverter());
xml = xstream.toXML(company);
System.out.println("\nUsing the CompanyConverter:\n" + xml);
}
}
Output
Features
• Ease of use - A high level facade is supplied that simplifies common
use cases.
• No mappings required - Custom 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.XStream doesn't care about the visibility of the fields
of the
class being serialized/deserialized. No getter and setter methods are
required for
the fields of the class to be serialized/deserialized.
• Full object graph support - Duplicate references encountered in the
objectmodel
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 stategies - Strategies can be registered
allowing
customization of how particular types are represented as XML.
• Error messages - When an exception occurs due to malformed XML,
detailed
diagnostics are provided to help isolate and fix the problem.
• Thread safe - Once the XStream instance has been created and
configured,
it may be shared across multiple threads allowing objects to be
serialized/deserialized concurrently. Note, that this only applies if
annotations are not auto-detected on-the-fly.
• Interoperability, Robustness and Extensibility
Limitations
• If you are using a JDK prior to 1.4x you must provide a no-argument
default
constructor in order to deserialize objects and instantiate them correctly.
• Auto-detection of annotations may cause race conditions. Preprocessing
annotations is safe though.
Typical Uses
• Data transport
• Object persistence
• Configuration
• Unit Tests
Conclusion
XStream is a fast and easy-to-use Java serialization tool that helps
in serializing
objects into XML and deserializing XML back into objects. It is simpler and
requires
less configuration.
References
http://xstream.codehaus.org