Ръководство за потребителя на XStream: Преобразуване на обекти в XML

1. Общ преглед

В този урок ще научим как да използваме библиотеката XStream за сериализиране на Java обекти в XML.

2. Характеристики

Има доста интересни ползи от използването на XStream за сериализиране и десериализиране на XML:

  • Конфигуриран правилно, той създава много чист XML
  • Предоставя значителни възможности за персонализиране на XML изхода
  • Поддръжка на обектни графики , включително кръгови препратки
  • За повечето случаи на използване екземплярът XStream е безопасен за нишки, след като бъде конфигуриран (има предупреждения при използване на анотации)
  • По време на обработката на изключения се предоставят ясни съобщения, които помагат за диагностицирането на проблеми
  • Започвайки с версия 1.4.7, имаме налични функции за защита , които забраняват сериализацията на определени типове

3. Настройка на проекта

За да използваме XStream в нашия проект, ще добавим следната зависимост на Maven:

 com.thoughtworks.xstream xstream 1.4.9 

4. Основна употреба

Класът XStream е фасада за API. Когато създаваме екземпляр на XStream , трябва да се погрижим и за проблемите с безопасността на нишките:

XStream xstream = new XStream();

След като даден екземпляр е създаден и конфигуриран, той може да бъде споделен в множество нишки за маршалиране / демаркиране, освен ако не активирате обработката на анотации.

4.1. Шофьори

Поддържат се няколко драйвера, като DomDriver , StaxDriver , XppDriver и др. Тези драйвери имат различни характеристики на производителност и използване на ресурсите.

Драйверът XPP3 се използва по подразбиране, но разбира се можем лесно да сменим драйвера:

XStream xstream = new XStream(new StaxDriver()); 

4.2. Генериране на XML

Нека започнем с дефиниране на прост POJO за - клиент :

public class Customer { private String firstName; private String lastName; private Date dob; // standard constructor, setters, and getters }

Нека сега генерираме XML представяне на обекта:

Customer customer = new Customer("John", "Doe", new Date()); String dataXml = xstream.toXML(customer);

Използвайки настройките по подразбиране, се получава следният изход:

 John Doe 1986-02-14 03:46:16.381 UTC  

От този изход можем ясно да видим, че съдържащият маркер по подразбиране използва напълно квалифицираното име на клас на Клиент .

Има много причини да решим, че поведението по подразбиране не отговаря на нашите нужди. Например, може да не ни е удобно да изложим структурата на пакета на нашето приложение. Също така генерираният XML е значително по-дълъг.

5. Псевдоними

Един псевдоним е име, което искате да използвате за елементи, а не с използване на име по подразбиране.

Например можем да заменим com.baeldung.pojo.Customer с клиент, като регистрираме псевдоним за класа Customer . Можем също да добавяме псевдоними за свойства на клас. Използвайки псевдоними, можем да направим нашия XML изход много по-четлив и по-малко специфичен за Java.

5.1. Класни псевдоними

Псевдонимите могат да бъдат регистрирани или програмно, или с помощта на анотации.

Нека сега поясняват нашия Клиент клас с @XStreamAlias :

@XStreamAlias("customer")

Сега трябва да конфигурираме нашия екземпляр да използва тази анотация:

xstream.processAnnotations(Customer.class);

Като алтернатива, ако искаме да конфигурираме псевдоним програмно, можем да използваме кода по-долу:

xstream.alias("customer", Customer.class);

Независимо дали се използва псевдоним или програмна конфигурация, изходът за обект на клиента ще бъде много по-чист:

 John Doe 1986-02-14 03:46:16.381 UTC  

5.2. Полеви псевдоними

Можем също да добавяме псевдоними за полета, използвайки същата анотация, използвана за класове за псевдоними. Например, ако искахме полето firstName да бъде заменено с fn в XML представлението, бихме могли да използваме следната анотация:

@XStreamAlias("fn") private String firstName;

Като алтернатива можем да постигнем една и съща цел програмно:

xstream.aliasField("fn", Customer.class, "firstName");

Методът aliasField приема три аргумента: псевдонимът, който искаме да използваме, класът, в който е дефинирано свойството, и името на свойството, което искаме да псевдоним.

Който и метод да се използва, изходът е същият:

 John Doe 1986-02-14 03:46:16.381 UTC 

5.3. Псевдоними по подразбиране

Има няколко предварително записани псевдоними за класове - ето няколко от тях:

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. Колекции

Сега ще добавим списък с ContactDetails вътре в класа 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.