1. Información general
En este tutorial, aprenderemos cómo usar la biblioteca XStream para serializar objetos Java a XML.
2. Características
Existen bastantes beneficios interesantes al usar XStream para serializar y deserializar XML:
- Configurado correctamente, produce XML muy limpio
- Proporciona oportunidades importantes para la personalización de la salida XML.
- Soporte para gráficos de objetos , incluidas referencias circulares
- Para la mayoría de los casos de uso, la instancia XStream es segura para subprocesos, una vez configurada (hay advertencias al usar anotaciones)
- Se proporcionan mensajes claros durante el manejo de excepciones para ayudar a diagnosticar problemas
- A partir de la versión 1.4.7, tenemos funciones de seguridad disponibles para no permitir la serialización de ciertos tipos.
3. Configuración del proyecto
Para usar XStream en nuestro proyecto, agregaremos la siguiente dependencia de Maven:
com.thoughtworks.xstream xstream 1.4.9
4. Uso básico
La clase XStream es una fachada para la API. Al crear una instancia de XStream , también debemos ocuparnos de los problemas de seguridad de los subprocesos:
XStream xstream = new XStream();
Una vez que se crea y se configura una instancia, se puede compartir en varios subprocesos para ordenar / desagrupar a menos que habilite el procesamiento de anotaciones.
4.1. Conductores
Se admiten varios controladores, como DomDriver , StaxDriver , XppDriver y más. Estos controladores tienen diferentes características de uso de recursos y rendimiento.
El controlador XPP3 se usa por defecto, pero, por supuesto, podemos cambiarlo fácilmente:
XStream xstream = new XStream(new StaxDriver());
4.2. Generando XML
Comencemos por definir un POJO simple para - Cliente :
public class Customer { private String firstName; private String lastName; private Date dob; // standard constructor, setters, and getters }
Generemos ahora una representación XML del objeto:
Customer customer = new Customer("John", "Doe", new Date()); String dataXml = xstream.toXML(customer);
Usando la configuración predeterminada, se produce la siguiente salida:
John Doe 1986-02-14 03:46:16.381 UTC
De esta salida, podemos ver claramente que la etiqueta contenedora usa el nombre de clase completamente calificado de Cliente de forma predeterminada .
Hay muchas razones por las que podríamos decidir que el comportamiento predeterminado no se adapta a nuestras necesidades. Por ejemplo, es posible que no nos sintamos cómodos al exponer la estructura del paquete de nuestra aplicación. Además, el XML generado es significativamente más largo.
5. Alias
Un alias es un nombre que deseamos usar para elementos en lugar de usar nombres predeterminados.
Por ejemplo, podemos reemplazar com.baeldung.pojo.Customer con customer registrando un alias para la clase Customer . También podemos agregar alias para las propiedades de una clase. Al usar alias, podemos hacer que nuestra salida XML sea mucho más legible y menos específica de Java.
5.1. Alias de clase
Los alias se pueden registrar mediante programación o mediante anotaciones.
Anotemos ahora nuestra clase de Cliente con @XStreamAlias :
@XStreamAlias("customer")
Ahora necesitamos configurar nuestra instancia para usar esta anotación:
xstream.processAnnotations(Customer.class);
Alternativamente, si deseamos configurar un alias mediante programación, podemos usar el siguiente código:
xstream.alias("customer", Customer.class);
Ya sea usando el alias o la configuración programática, la salida para un objeto Cliente será mucho más limpia:
John Doe 1986-02-14 03:46:16.381 UTC
5.2. Alias de campo
También podemos agregar alias para los campos usando la misma anotación que se usa para las clases de alias. Por ejemplo, si quisiéramos que el campo firstName se reemplazara con fn en la representación XML, podríamos usar la siguiente anotación:
@XStreamAlias("fn") private String firstName;
Alternativamente, podemos lograr el mismo objetivo mediante programación:
xstream.aliasField("fn", Customer.class, "firstName");
El método aliasField acepta tres argumentos: el alias que deseamos usar, la clase en la que se define la propiedad y el nombre de la propiedad que deseamos alias.
Cualquiera que sea el método que se utilice, la salida es la misma:
John Doe 1986-02-14 03:46:16.381 UTC
5.3. Alias predeterminados
Hay varios alias prerregistrados para las clases; aquí hay algunos de estos:
alias("float", Float.class); alias("date", Date.class); alias("gregorian-calendar", Calendar.class); alias("url", URL.class); alias("list", List.class); alias("locale", Locale.class); alias("currency", Currency.class);
6. Colecciones
Ahora agregaremos una lista de ContactDetails dentro de la clase Customer .
private List contactDetailsList;
With default settings for collection handling, this is the output:
John Doe 1986-02-14 04:14:05.874 UTC 6673543265 0124-2460311 4676543565 0120-223312
Let's suppose we need to omit the contactDetailsList parent tags, and we just want each ContactDetails element to be a child of the customer element. Let us modify our example again:
xstream.addImplicitCollection(Customer.class, "contactDetailsList");
Now, when the XML is generated, the root tags are omitted, resulting in the XML below:
John Doe 1986-02-14 04:14:20.541 UTC 6673543265 0124-2460311 4676543565 0120-223312
The same can also be achieved using annotations:
@XStreamImplicit private List contactDetailsList;
7. Converters
XStream uses a map of Converter instances, each with its own conversion strategy. These convert supplied data to a particular format in XML and back again.
In addition to using the default converters, we can modify the defaults or register custom converters.
7.1. Modifying an Existing Converter
Suppose we weren't happy with the way the dob tags were generatedusing the default settings. We can modify the custom converter for Date provided by XStream (DateConverter):
xstream.registerConverter(new DateConverter("dd-MM-yyyy", null));
The above will produce the output in “dd-MM-yyyy” format:
John Doe 14-02-1986
7.2. Custom Converters
We can also create a custom converter to accomplish the same output as in the previous section:
public class MyDateConverter implements Converter { private SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy"); @Override public boolean canConvert(Class clazz) { return Date.class.isAssignableFrom(clazz); } @Override public void marshal( Object value, HierarchicalStreamWriter writer, MarshallingContext arg2) { Date date = (Date)value; writer.setValue(formatter.format(date)); } // other methods }
Finally, we register our MyDateConverter class as below:
xstream.registerConverter(new MyDateConverter());
We can also create converters that implement the SingleValueConverter interface, which is designed to convert an object into a string.
public class MySingleValueConverter implements SingleValueConverter { @Override public boolean canConvert(Class clazz) { return Customer.class.isAssignableFrom(clazz); } @Override public String toString(Object obj) { SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy"); Date date = ((Customer) obj).getDob(); return ((Customer) obj).getFirstName() + "," + ((Customer) obj).getLastName() + "," + formatter.format(date); } // other methods }
Finally, we register MySingleValueConverter:
xstream.registerConverter(new MySingleValueConverter());
Using MySingleValueConverter, the XML output for a Customer is as follows:
John,Doe,14-02-1986
7.3. Converter Priority
When registering Converter objects, is is possible to set their priority level, as well.
From the XStream javadocs:
The converters can be registered with an explicit priority. By default they are registered with XStream.PRIORITY_NORMAL. Converters of same priority will be used in the reverse sequence they have been registered. The default converter, i.e. the converter which will be used if no other registered converter is suitable, can be registered with priority XStream.PRIORITY_VERY_LOW. XStream uses by default the ReflectionConverter as the fallback converter.
The API provides several named priority values:
private static final int PRIORITY_NORMAL = 0; private static final int PRIORITY_LOW = -10; private static final int PRIORITY_VERY_LOW = -20;
8.Omitting Fields
We can omit fields from our generated XML using either annotations or programmatic configuration. In order to omit a field using an annotation, we simply apply the @XStreamOmitField annotation to the field in question:
@XStreamOmitField private String firstName;
In order to omit the field programmatically, we use the following method:
xstream.omitField(Customer.class, "firstName");
Whichever method we select, the output is the same:
Doe 14-02-1986
9. Attribute Fields
Sometimes we may wish to serialize a field as an attribute of an element rather than as element itself. Suppose we add a contactType field:
private String contactType;
If we want to set contactType as an XML attribute, we can use the @XStreamAsAttribute annotation:
@XStreamAsAttribute private String contactType;
Alternatively, we can accomplish the same goal programmatically:
xstream.useAttributeFor(ContactDetails.class, "contactType");
The output of either of the above methods is the same:
6673543265 0124-2460311
10. Concurrency
XStream's processing model presents some challenges. Once the instance is configured, it is thread-safe.
It is important to note that processing of annotations modifies the configuration just before marshalling/unmarshalling. And so – if we require the instance to be configured on-the-fly using annotations, it is generally a good idea to use a separate XStream instance for each thread.
11. Conclusion
In this article, we covered the basics of using XStream to convert objects to XML. We also learned about customizations we can use to ensure the XML output meets our needs. Finally, we looked at thread-safety problems with annotations.
In the next article in this series, we will learn about converting XML back to Java objects.
The complete source code for this article can be downloaded from the linked GitHub repository.