Посібник користувача 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 з замовником шляхом реєстрації псевдоніма для клієнтів класу. Ми також можемо додавати псевдоніми для властивостей класу. Використовуючи псевдоніми, ми можемо зробити наш вихід 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.