Статьи

Хорошо известный секрет JAXB

Вступление

Я заново открыл библиотеку, которую Java предлагает массам. Когда я впервые прочитал спецификацию, я был смущен и подумал, что мне нужны все эти специальные инструменты для реализации. Недавно я обнаружил, что все, что нужно, это несколько аннотаций и POJO.

JAXB

JAXB означает Java Architecture для привязки XML. Эта архитектура позволяет разработчику превращать данные из класса в XML-представление. Это называется сортировкой. Архитектура также позволяет разработчику полностью изменить процесс, превратив представление XML в класс. Это называется демаршаллингом. Существуют инструменты, которые могут создавать классы Java из файлов схемы XML. Инструмент называется xjc. Есть еще один инструмент, который создает файлы xsd с помощью schemagen.

сортировочный

Маршаллинг и демаршаллинг происходят в нескольких местах на Яве. Первым, кого я столкнулся с этим, был RMI. Объекты отправляются через использование в качестве параметров для удаленных вызовов методов, отсюда и название Remote Method Invocation (RMI). Другое место, где это происходит, — это запись объектов в поток. Потоки, которые реализуют это, являются ObjectOutputStream и ObjectInputStream. Другое место, где это происходит, это классы ORM. Другой способ, конечно, — написать XML-представление экземпляра. Классы, которые хотят маршалировать, должны реализовывать Serializable, и все его атрибуты-члены должны также реализовывать Serializable, за исключением классов, проходящих через JAXB. Сериализуемый — это маркерный интерфейс. У него нет методов для реализации, но он показывает, что класс можно сериализовать или маршалировать. Объекту, который был маршаллирован, подвергали свои данные постоянному определению. У немаршаллированных объектов данные считывались из постоянного состояния и соединялись с классом. Это делает classpath очень важным. Для забавы, допустимая запись в classpath — http: // ip: port / path / to / jar . Я полагаю, что некоторые организации используют это, централизуя свои jar-файлы, и последняя версия только что загружена.

пример

Я использовал Maven и Spring, чтобы сделать этот пример. Причина заключалась не в том, чтобы сделать его более сложным, а в том, чтобы сделать код более понятным для чтения и сосредоточиться на использовании технологии, которую я показываю. Зависимости в файле pom.xml приведены ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
 
    <dependency>
      <groupId>com.sun.xml.bind</groupId>
      <artifactId>jaxb-impl</artifactId>
      <version>2.2.8-b01</version>
    </dependency>
 
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>3.2.3.RELEASE</version>
    </dependency>
 
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.2.3.RELEASE</version>
    </dependency>
 
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>3.2.3.RELEASE</version>
      </dependency>
 
  </dependencies>

Замечательная вещь о JAXB состоит в том, что он использует POJO. Contact.java — это центральный класс POJO в коллекции из трех человек.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package org.mathison.jaxb.beans;
 
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
 
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Contact {
    private String lastName;
    private String firstName;
    private String middleName;
    private String jobTitle;
 
    @XmlElementWrapper(name = "addresses")
    @XmlElement(name = "address")
    private List<Address> addresses;
 
    @XmlElementWrapper(name = "phone-numbers")
    @XmlElement(name = "phone-number")
    private List<PhoneNumber> numbers;
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getMiddleName() {
        return middleName;
    }
 
    public void setMiddleName(String middleName) {
        this.middleName = middleName;
    }
 
    public String getJobTitle() {
        return jobTitle;
    }
 
    public void setJobTitle(String jobTitle) {
        this.jobTitle = jobTitle;
    }
 
    public List<Address> getAddresses() {
        return addresses;
    }
 
    public void setAddresses(List<Address> addresses) {
        this.addresses = addresses;
    }
 
    public List<PhoneNumber> getNumbers() {
        return numbers;
    }
 
    public void setNumbers(List<PhoneNumber> numbers) {
        this.numbers = numbers;
    }
 
    @Override
    public String toString() {
        return "Contact{" + "lastName=" + lastName + ", firstName="
                          + firstName + ", middleName=" + middleName
                          + ", jobTitle=" + jobTitle + ", addresses="
                          + addresses + ", numbers=" + numbers + '}';
    }
 
    @Override
    public int hashCode() {
        int hash = 3;
 
        hash = 23 * hash + (this.lastName != null ?
                            this.lastName.hashCode() : 0);
 
        hash = 23 * hash + (this.firstName != null ?
                            this.firstName.hashCode() : 0);
 
        hash = 23 * hash + (this.middleName != null ?
                            this.middleName.hashCode() : 0);
 
        hash = 23 * hash + (this.jobTitle != null ?
                            this.jobTitle.hashCode() : 0);
 
        hash = 23 * hash + (this.addresses != null ?
                            this.addresses.hashCode() : 0);
 
        hash = 23 * hash + (this.numbers != null ?
                            this.numbers.hashCode() : 0);
 
        return hash;
    }
 
    @Override
    public boolean equals(Object obj) {
 
        if (obj == null) {
            return false;
        }
 
        if (getClass() != obj.getClass()) {
            return false;
        }
 
        final Contact other = (Contact) obj;
 
        if ((this.lastName == null) ? (other.lastName != null) :
             !this.lastName.equals(other.lastName)) {
 
            return false;
        }
 
        if ((this.firstName == null) ? (other.firstName != null) :
             !this.firstName.equals(other.firstName)) {
            return false;
        }
 
        if ((this.middleName == null) ? (other.middleName != null) :
             !this.middleName.equals(other.middleName)) {
            return false;
        }
 
        if ((this.jobTitle == null) ? (other.jobTitle != null) :
             !this.jobTitle.equals(other.jobTitle)) {
            return false;
        }
 
        if(!listEquals(this.addresses, other.addresses)) {
            return false;
        }
 
        if(!listEquals(this.numbers, other.numbers)) {
            return false;
        }
 
        return true;
 
    }
 
    private boolean listEquals(List first, List second) {
        for(Object o: first) {
            if(!second.contains(o)) {
                return false;
            }
        }
 
        return true;
    }
 
}

Основная часть, на которую стоит обратить внимание, это аннотации. @XmlRootElement определяет, что это начало класса. @XmlAccessorType (XmlAccessType.FIELD) сообщает архитектуре, что поля будут использоваться для определения элементов в xml. Аннотации также могут быть размещены на получателях. Если аннотация не используется, JAXB запутывается в том, что использовать. Для случаев, когда список присутствует, @XmlElementWrapper используется, чтобы сообщить JAXB, каким будет внешний тег. Например, есть список адресов. Оболочка принимает параметр с именем «name» и заполняется «address». Когда рендеринг XML, будет добавлен тег «адреса», обернутый вокруг коллекции адресов. Аннотация @XmlElement используется, когда нужно изменить тег свойства. Чтобы вернуться к нашему списку адресов, аннотация переопределила список адресов на «адрес». Это приведет к тому, что каждый адресный объект будет иметь тег «address» вместо «address», который уже занят. Тот же шаблон используется для чисел. Остальные свойства будут иметь теги, которые соответствуют их названию. Например, lastName будет превращено в тег «lastName». Другие два POJO, PhoneNumber.java и Address.java имеют открытые классы enum. Вот PhoneNumber.java:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package org.mathison.jaxb.beans;
 
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
 
@XmlRootElement
public class PhoneNumber {
 
    @XmlType(name="phone-type")
    public enum Type {
        HOME,
        WORK,
        HOME_FAX,
        WORK_FAX;
    }
 
    private Type type;
    private String number;
 
    public Type getType() {
        return type;
    }
 
    public void setType(Type type) {
        this.type = type;
    }
 
    public String getNumber() {
        return number;
    }
 
    public void setNumber(String number) {
        this.number = number;
    }
 
    @Override
    public String toString() {
        return "PhoneNumber{" + "type=" + type + ", number=" + number + '}';
    }
 
    @Override
    public int hashCode() {
        int hash = 7;
 
        hash = 37 * hash + (this.type != null ? this.type.hashCode() : 0);
        hash = 37 * hash + (this.number != null ?
                            this.number.hashCode() : 0);
 
        return hash;
 
    }
 
    @Override
 
    public boolean equals(Object obj) {
 
        if (obj == null) {
            return false;
        }
 
        if (getClass() != obj.getClass()) {
            return false;
        }
 
        final PhoneNumber other = (PhoneNumber) obj;
 
        if (this.type != other.type) {
            return false;
        }
 
        if ((this.number == null) ? (other.number != null) :
             !this.number.equals(other.number)) {
            return false;
        }
 
        return true;
    }
 
}

Примечание примечания является @XmlType. Это говорит JAXB, что класс ограниченного числа значений. Требуется параметр имени. Последний POJO также использует @XmlType для определения своего открытого класса enum. Его можно найти по адресу Address.java .

Собираем все вместе

Со всей этой аннотацией и определением класса пришло время объединить все это в один основной класс. Вот App.java, основной класс:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package org.mathison.jaxb.app;
 
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.mathison.jaxb.beans.Contact;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
 
public class App
{
 
    public static void main( String[] args )
    {
 
        ApplicationContext cxt = new GenericXmlApplicationContext("jaxb.xml");
        Contact contact = cxt.getBean("contact", Contact.class);
        StringWriter writer = new StringWriter();
 
        try {
            JAXBContext context = JAXBContext.newInstance(Contact.class);
 
            //create xml from an instance from Contact
 
            Marshaller m = context.createMarshaller();
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            m.marshal(contact, writer);
 
            String xml = writer.getBuffer().toString();
 
            System.out.println(xml);
 
            //Take xml to Contact
 
            StringReader reader = new StringReader(xml);
            Unmarshaller u = context.createUnmarshaller();
 
            Contact fromXml = (Contact)u.unmarshal(reader);
 
            System.out.println("Are the instances equivalent: " +
                                contact.equals(fromXml));
 
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}

Сначала экземпляр контакта извлекается из ApplicationContext. Во-вторых, создается экземпляр JAXBContext с классом Contact в качестве корневого класса. Контекст проанализирует структуру класса и создаст контекст, который может маршалировать или отменять маршалинг классов Contact, Address и PhoneNumber. В следующем разделе маршаллер создается из JAXBContext. Свойство Marshaller.JAXB_FORMATTED_OUTPUT имеет значение true. Это создает вывод XML, который отформатирован. Если свойство не задано, XML будет выглядеть как одна строка текста. Маршаллер призван к маршалловому контакту и записан на StringWriter. Затем XML печатается в System.out. Вывод должен выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<contact>
 
    <lastName>Mathison</lastName>
    <firstName>Daryl</firstName>
    <middleName>Bob</middleName>
    <jobTitle>Developer</jobTitle>
    <addresses>
        <address>
            <addressLine>123 Willow View</addressLine>
            <city>Cibolo</city>
            <state>TX</state>
            <type>HOME</type>
            <zipCode>78228</zipCode>
        </address>
        <address>
            <addressLine>411 Grieg</addressLine>
            <city>San Antonio</city>
            <state>TX</state>
            <type>WORK</type>
            <zipCode>78228</zipCode>
        </address>
    </addresses>
 
    <phone-numbers>
        <phone-number>
            <number>210-123-4567</number>
            <type>WORK</type>
        </phone-number>
 
        <phone-number>
            <number>210-345-1111</number>
            <type>HOME</type>
        </phone-number>
    </phone-numbers>
 
</contact>

В следующем разделе xml возвращается в экземпляр Contact с его данными. Unmarshaller создается JAXBContext. Затем unmarshaller передается StringReader с только что созданным XML в качестве его содержимого. Unmarshaller возвращает объект, который приведен к контакту. Исходный экземпляр Contact проверяется на соответствие новому экземпляру Contact, чтобы определить, являются ли они эквивалентными. Вывод должен показать:

1
Are the instances equivalent: true.

Резюме

В этом примере экземпляр Contact был преобразован в XML, а полученный XML был возвращен в экземпляр Contact с помощью JAXB. JAXB — это архитектура, которая отображает состояние объекта в XML и отображает XML обратно в объект.

использованная литература