Вступление
Во все более взаимосвязанной экосистеме программных систем коммуникация между ними становится еще более важной. В свою очередь, было разработано несколько технологий для упаковки данных, передаваемых или совместно используемых этими множеством различных систем.
Расширяемый язык разметки , широко известный как XML , является одним из способов упаковки данных для передачи. XML - это язык форматирования документов, который был разработан в 1990-х годах, поскольку HTML не позволяет определять новые текстовые элементы, т. Е. Он не расширяемый. Данные в XML не только расширяемы, но и обладают самоописанием, что делает их удобочитаемыми и легкими для понимания.
В этом посте мы исследуем манипуляции с XML в Java с помощью библиотеки Джексона .
Преимущества и недостатки XML
XML по-прежнему популярен и используется в некоторых системах, поскольку он имеет некоторые преимущества, но также появились новые технологии для устранения некоторых из его недостатков.
Некоторые из преимуществ XML включают в себя:
- XML не привязан к одной платформе или языку программирования и может легко использоваться во многих различных системах. Это делает его подходящим для облегчения связи между системами с различными конфигурациями аппаратного и программного обеспечения.
- Данные, содержащиеся в документе XML, можно проверить с помощью определения типа документа (DTD) или схемы XML. Это набор объявлений разметки, которые определяют строительные блоки XML-документа.
- Благодаря поддержке Unicode, XML может содержать информацию, написанную на любом языке или в любом формате, без потери какой-либо информации или содержимого в процессе.
- Благодаря совместимости с HTML, с помощью HTML легко читать и отображать данные, содержащиеся в XML-документе.
- Информация, хранящаяся в документе XML, может быть изменена в любой момент, не влияя на представление данных через другие носители, такие как HTML.
Некоторые из недостатков XML, которые были устранены с помощью новых технологий, включают:
- Синтаксис довольно избыточен и подробен по сравнению с другими форматами, такими как JSON, который является кратким и понятным.
- Из-за синтаксиса и подробного характера XML-документы обычно имеют большой размер, что может привести к дополнительным расходам на хранение и транспортировку.
- Он не поддерживает массивы.
Библиотеки XML
Управление XML в Java может быть утомительным процессом, поэтому для облегчения процесса и ускорения разработки мы можем использовать различные библиотеки. Они включают:
- Eaxy - небольшая и простая библиотека для создания, обработки, анализа и поиска XML.
- Архитектура Java для привязки XML (JAXB) - это структура для сопоставления классов Java с представлениями XML посредством преобразования объектов Java в XML и преобразования XML в объекты Java. Это часть платформы Java SE.
- Jackson - это библиотека для обработки JSON в системах Java, которая теперь поддерживает XML начиная с версии 2.
- DOM4J - это библиотека с эффективным использованием памяти для синтаксического анализа XML, XPath и XSLT (расширяемый язык таблиц стилей).
- JDom - библиотека синтаксического анализа XML с поддержкой XPath и XSLT.
Что такое Джексон?
Проект Jackson представляет собой набор инструментов обработки данных для языка Java и платформы JVM. Он поддерживает широкий спектр форматов данных, таких как CSV, Java Properties, XML и YAML, через компоненты расширения, поддерживающие конкретный язык.
Компонент Jackson XML предназначен для чтения и записи XML-данных путем имитации работы JAXB, хотя и не окончательно.
В этой статье мы будем использовать библиотеку Джексона для сериализации объектов Java в XML и их десериализации обратно в объекты Java.
Настройка проекта
Во-первых, давайте создадим новый проект Maven:
$ mvn archetype:generate -DgroupId=com.stackabuse -DartifactId=xmltutorial -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
pom.xml
наш проект, давайте добавим зависимость Джексона в наш файл
pom.xml. Удалите существующий раздел зависимостей и замените его
следующим:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- Jackson dependency for XML manipulation -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--
This plugin configuration will enable Maven to include the project dependencies
in the produced jar file.
It also enables us to run the jar file using `java -jar command`
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.stackabuse.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Теперь мы можем протестировать настроенный проект, выполнив следующие команды:
$ mvn package
$ java -jar target/java -jar target/xmltutorial-1.0.jar
Результатом должно быть Hello World!
напечатано на нашем терминале,
показывая, что наш проект готов к следующему этапу проекта.
Сериализация объектов Java в XML
У объектов Java есть атрибуты и методы для управления этими атрибутами. В отношении документа XML элементы в документе могут быть сопоставлены с атрибутами объекта Java.
В процессе сериализации атрибуты объекта преобразуются в элементы XML и сохраняются в документе XML.
Мы будем использовать PhoneDetails
который будет определять информацию
о конкретной модели телефона, такую как ее имя, размер дисплея и емкость
внутренней памяти. В нашем классе это будут атрибуты, но в нашем
XML-документе эти детали будут содержаться в тегах или элементах.
Давайте начнем с определения PhoneDetails
который будет использоваться
для создания наших объектов:
public class PhoneDetails {
private String name;
private String displaySize;
private String memory;
// getters and setters
}
С нашим набором объектов давайте App.java
наш App.java и добавим
функцию для обработки сериализации в XML:
/**
* This function writes serializes the Java object into XML and writes it
* into an XML file.
*/
public static void serializeToXML() {
try {
XmlMapper xmlMapper = new XmlMapper();
// serialize our Object into XML string
String xmlString = xmlMapper.writeValueAsString(new PhoneDetails("OnePlus", "6.4", "6/64 GB"));
// write to the console
System.out.println(xmlString);
// write XML string to file
File xmlOutput = new File("serialized.xml");
FileWriter fileWriter = new FileWriter(xmlOutput);
fileWriter.write(xmlString);
fileWriter.close();
} catch (JsonProcessingException e) {
// handle exception
} catch (IOException e) {
// handle exception
}
}
public static void main(String[] args) {
System.out.println("Serializing to XML...");
serializeToXML();
}
Давайте запакуем и запустим наш проект еще раз:
$ mvn package
$ java -jar target/xmltutorial-1.0.jar
Вывод на терминал:
<PhoneDetails><name>OnePlus</name><displaySize>6.4</displaySize><memory>6/64 GB</memory></PhoneDetails>
В корневой папке нашего проекта serialized.xml
содержащий эту
информацию. Мы успешно сериализовали наш объект Java в XML и записали
его в файл XML.
В нашей функции serializeToXML()
мы создаем XmlMapper
, который
является дочерним классом для ObjectMapper
используемого в
сериализации JSON. Этот класс преобразует наш объект Java в вывод XML,
который теперь можно записать в файл.
Десериализация из XML
Джексон также позволяет нам читать содержимое XML-файла и десериализовать XML-строку обратно в объект Java. В нашем примере мы прочитаем XML-документ, содержащий подробную информацию о телефоне, и воспользуемся Джексоном, чтобы извлечь эти данные и использовать их для создания объектов Java, содержащих ту же информацию.
Во-первых, давайте создадим XML-документ, соответствующий нашему классу,
для чтения. Создайте to_deserialize.xml
со следующим содержимым:
<PhoneDetails>
<name>iPhone</name>
<displaySize>6.2</displaySize>
<memory>3/64 GB</memory>
</PhoneDetails>
Давайте добавим функцию deserializeFromXML()
для десериализации
XML-файла выше в объект Java:
public static void deserializeFromXML() {
try {
XmlMapper xmlMapper = new XmlMapper();
// read file and put contents into the string
String readContent = new String(Files.readAllBytes(Paths.get("to_deserialize.xml")));
// deserialize from the XML into a Phone object
PhoneDetails deserializedData = xmlMapper.readValue(readContent, PhoneDetails.class);
// Print object details
System.out.println("Deserialized data: ");
System.out.println("\tName: " + deserializedData.getName());
System.out.println("\tMemory: " + deserializedData.getMemory());
System.out.println("\tDisplay Size: " + deserializedData.getDisplaySize());
} catch (IOException e) {
// handle the exception
}
}
public static void main(String[] args) {
System.out.println("Deserializing from XML...");
deserializeFromXML();
}
Мы упаковываем и запускаем наш проект как обычно, и на выходе получаем:
Deserializing from XML...
Deserialized data:
Name: iPhone
Memory: 3/64 GB
Display Size: 6.2
Наш XML-файл был успешно десериализован, и все данные были извлечены с помощью библиотеки Джексона.
Аннотации Джексона
Аннотации используются для добавления метаданных в наш Java-код, и они не имеют прямого влияния на выполнение кода, к которому они прикреплены. Они используются для передачи инструкций компилятору во время компиляции и выполнения.
Джексон использует аннотации для различных функций, таких как определение того, сопоставляем ли мы XML или JSON, определение порядка атрибутов и полей в нашем выводе или их имен.
Эти аннотации обычно применяются в наших объектах Java POJO (обычные
старые объекты Java). Например, мы можем аннотировать наш PhoneDetails
следующим образом:
public class PhoneDetails {
@JsonProperty("phone_name")
private String name;
@JsonProperty("display_size")
private String displaySize;
@JsonProperty("internal_memory")
private String memory;
// rest of the code remains as is
}
@JsonProperty
помогает определить имена полей в нашем XML-файле. После
добавления этой аннотации теги в наших выходных и входных файлах XML
должны будут напоминать строки в аннотации следующим образом:
<PhoneDetails>
<phone_name>OnePlus</phone_name>
<display_size>6.4</display_size>
<internal_memory>6/64 GB</internal_memory>
</PhoneDetails>
Еще одна примечательная аннотация - @JacksonXmlText
которая указывает,
что элемент должен отображаться как простой текст без каких-либо тегов
или другого элемента, содержащего его.
@JacksonXmlProperty
можно использовать для управления деталями
отображаемого атрибута или элемента. Такие детали могут включать в себя
пространство имен элемента. Пространства имен - это способ присвоения
элементов определенной группе.
Одно из основных применений пространств имен - избежать конфликтов при использовании похожих тегов в документе, они помогают изолировать теги по группе, чтобы устранить любую двусмысленность, которая может возникнуть при масштабировании XML-документов.
Порядок свойств также можно указать с помощью аннотации
@JsonPropertyOrder
Например, чтобы изменить порядок элементов в выводе
XML-документа, аннотация используется следующим образом:
@JsonPropertyOrder({ "internal_memory", "display_size", "phone_name" })
public class PhoneDetails {
@JsonProperty("phone_name")
private String name;
@JsonProperty("display_size")
private String displaySize;
@JsonProperty("internal_memory")
private String memory;
...
Результат сериализации в XML теперь будет:
<PhoneDetails>
<internal_memory>6/64 GB</internal_memory>
<display_size>6.4</display_size>
<phone_name>OnePlus</phone_name>
</PhoneDetails>
Если в объектах Java есть поля, которые мы не хотим сериализовать, мы
можем использовать @JsonIgnore
и поля будут опущены во время
сериализации и десериализации.
Аннотации Джексона полезны для определения и управления процессом сериализации и десериализации в различных форматах, таких как XML, JSON и YAML. Некоторые аннотации работают для всех форматов, а некоторые привязаны к определенному типу файла.
Больше аннотаций Джексона и их использования можно найти в этой официальной вики на Github.
Управление вложенными элементами и списками в XML
Узнав об аннотациях, давайте улучшим наш XML-файл, добавив вложенные элементы и циклы, и изменим наш код, чтобы сериализовать и десериализовать следующую обновленную структуру:
<PhoneDetails>
<internal_memory>3/64 GB</internal_memory>
<display_size>6.2</display_size>
<phone_name>iPhone X</phone_name>
<manufacturer>
<manufacturer_name>Apple</manufacturer_name>
<country>USA</country>
<other_phones>
<phone>iPhone 8</phone>
<phone>iPhone 7</phone>
<phone>iPhone 6</phone>
</other_phones>
</manufacturer>
</PhoneDetails>
В этой новой структуре мы ввели вложенный Manufacturer
который также
включает список элементов. С нашим текущим кодом мы не можем извлечь или
создать новый вложенный раздел.
Чтобы исправить это, требуется новый класс для обработки вложенного
элемента, и для этого он является частью нашего нового класса
Manufacturer
// define the order of elements
@JsonPropertyOrder({ "manufacturer_name", "country", "other_phones" })
public class Manufacturer {
@JsonProperty("manufacturer_name")
private String name;
@JsonProperty("country")
private String country;
// new annotation
@JacksonXmlElementWrapper(localName="other_phones")
private List<String> phone;
...
Он очень похож на наш PhoneDetails
но теперь мы ввели новую аннотацию:
@JacksonXmlElementWrapper
. Цель этой аннотации - определить,
использует ли коллекция элементов элемент оболочки или нет, и может
использоваться для определения локального имени и пространства имен
элементов оболочки.
В нашем примере мы используем аннотацию для определения элемента, содержащего список элементов, и тега, который будет использоваться для этого элемента. Это будет использоваться при сериализации и десериализации наших файлов XML.
Это изменение в нашей структуре XML и введение этого класса требует,
чтобы мы изменили наш PhoneDetails
чтобы отразить:
// existing code remains
public class PhoneDetails {
// existing code remains
@JsonProperty("manufacturer")
private Manufacturer manufacturer;
// standard getters and setters for the new element
...
Наш PhoneDetails
теперь может включать информацию о производителе
телефона.
Затем мы обновляем наш метод serializeToXML()
public static void serializeToXML() {
try {
XmlMapper xmlMapper = new XmlMapper();
// create a list of other phones
List<String> otherPhones = Arrays.asList("OnePlus 6T", "OnePlus 5T", "OnePlus 5");
// create the manufacturer object
Manufacturer manufacturer = new Manufacturer("OnePlus", "China", otherPhones);
// serialize our new Object into XML string
String xmlString = xmlMapper
.writeValueAsString(new PhoneDetails("OnePlus", "6.4", "6/64 GB", manufacturer));
// write to the console
System.out.println(xmlString);
// write XML string to file
File xmlOutput = new File("serialized.xml");
FileWriter fileWriter = new FileWriter(xmlOutput);
fileWriter.write(xmlString);
fileWriter.close();
} catch (JsonProcessingException e) {
// handle the exception
} catch (IOException e) {
// handle the exception
}
}
Результат сериализации нового объекта PhoneDetails
с информацией
Manufacturer
Serializing to XML...
<PhoneDetails><internal_memory>6/64 GB</internal_memory><display_size>6.4</display_size><phone_name>OnePlus</phone_name><manufacturer><manufacturer_name>OnePlus</manufacturer_name><country>China</country><other_phones><phones>OnePlus 6T</phones><phones>OnePlus 5T</phones><phones>OnePlus 5</phones></other_phones></manufacturer></PhoneDetails>
Оно работает! Наша deserializeFromXML()
, с другой стороны, не требует
серьезного обновления, поскольку PhoneDetails
при десериализации также
будет включать информацию о производителе.
Давайте добавим следующий код, чтобы распечатать данные производителя, чтобы быть уверенным:
// existing code remains
// Print object details
System.out.println("Deserialized data: ");
System.out.println("\tName: " + deserializedData.getName());
System.out.println("\tMemory: " + deserializedData.getMemory());
System.out.println("\tDisplay Size: " + deserializedData.getDisplaySize());
System.out.println("\tManufacturer Name: " + deserializedData.getManufacturer().getName());
System.out.println("\tManufacturer Country: " + deserializedData.getManufacturer().getCountry());
System.out.println("\tManufacturer Other Phones: " + deserializedData.getManufacturer().getPhone().toString());
// existing code remains
Выход:
Deserializing from XML...
Deserialized data:
Name: iPhone X
Memory: 3/64 GB
Display Size: 6.2
Manufacturer Name: Apple
Manufacturer Country: USA
Manufacturer Other Phones: [iPhone 8, iPhone 7, iPhone 6]
Процесс десериализации проходит без проблем, а новые данные производителя были извлечены из нашего обновленного файла XML.
Заключение
В этом посте мы узнали об XML и о том, как сериализовать данные в XML-документы, а также о десериализации для извлечения данных из XML-документов.
Мы также узнали об аннотациях и о том, как Джексон использует аннотации в процессе сериализации и десериализации.
XML по-прежнему широко используется в различных системах, с которыми мы можем время от времени взаимодействовать, поэтому для взаимодействия с ними нам потребуется время от времени сериализовать и десериализовать XML-документы. Мы также можем использовать XML API в наших проектах Java, открывая конечные точки REST, и использовать Jackson для преобразования входных данных XML в выходные данные JSON.
Исходный код этого поста доступен на Github для справки.