В этом посте рассматривается сопоставление объектов JAXB с объектами бизнес-доменов с помощью Orika . Ранее в этом месяце я описал тот же вариант использования картографирования, используя основанный на отражении Dozer . В этом посте я буду предполагать, что те же самые примеры классов должны быть отображены, но они будут отображаться с использованием Orika вместо Dozer .
Dozer и Orika предназначены для решения одной и той же проблемы: автоматическое сопоставление двух объектов «данных», которые не имеют общего наследования, но представляют одинаковые поля данных. Dozer использует отражение для достижения этой цели, в то время как Orika использует отражение и манипулирование байт-кодом для достижения этой цели. Девиз Orika — «проще, легче и быстрее отображать Java-бин».
Orika имеет лицензию Apache версии 2 и может быть загружена по адресу https://github.com/orika-mapper/orika/archive/master.zip (sources) или по адресу http://search.maven.org/#search. | га | 1 | орика (двоичные файлы). У Orika есть зависимости от Javassist (для манипулирования байт-кодом), SLF4J и paranamer (для доступа к именам параметров метода / конструктора во время выполнения). Две из этих трех зависимостей (JavaAssist и paranamer, но не SLF4J) связаны в orika-core-1.4.4-deps-included.jar
. Если зависимости уже доступны, вместо orika-core-1.4.4.jar
можно использовать более тонкий orika-core-1.4.4.jar
. Как следует из названий этих JAR-файлов, я использую Orika 1.4.4 для своих примеров в этом посте.
В своем посте « Dozer: отображение объектов JAXB на объекты бизнеса / домена» я рассмотрел причины, по которым использование экземпляров классов, генерируемых JAXB, в качестве объектов бизнеса или домена часто нежелательно. Затем я показал «традиционные» способы отображения между классами, сгенерированными JAXB, и пользовательскими классами данных, чтобы данные могли передаваться по всему приложению в объектах данных бизнес-области. В этом посте я буду использовать тот же подход, но Орика будет делать сопоставление, а не настраивать сопоставление или использовать Dozer для сопоставления. Для удобства я включил здесь списки цен для сгенерированных JAXB классов com.blogspot.marxsoftware.AddressType
и com.blogspot.marxsoftware.PersonType
а также переименованные пользовательские классы данных dustin.examples.orikademo.Address
и dustin.examples.orikademo.Person
.
Генерируемый JAXB AddressType.java
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
164
165
166
167
168
169
170
171
|
// // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4-2 // See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> // Any modifications to this file will be lost upon recompilation of the source schema. // Generated on: 2013.12.03 at 11:44:32 PM MST // package com.blogspot.marxsoftware; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlType; /** * <p>Java class for AddressType complex type. * * <p>The following schema fragment specifies the expected content contained within this class. * * <pre> * <complexType name="AddressType"> * <complexContent> * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> * <attribute name="streetAddress1" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="streetAddress2" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="city" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="state" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="zipcode" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * </restriction> * </complexContent> * </complexType> * </pre> * * */ @XmlAccessorType (XmlAccessType.FIELD) @XmlType (name = "AddressType" ) public class AddressType { @XmlAttribute (name = "streetAddress1" , required = true ) protected String streetAddress1; @XmlAttribute (name = "streetAddress2" ) protected String streetAddress2; @XmlAttribute (name = "city" , required = true ) protected String city; @XmlAttribute (name = "state" , required = true ) protected String state; @XmlAttribute (name = "zipcode" , required = true ) protected String zipcode; /** * Gets the value of the streetAddress1 property. * * @return * possible object is * {@link String } * */ public String getStreetAddress1() { return streetAddress1; } /** * Sets the value of the streetAddress1 property. * * @param value * allowed object is * {@link String } * */ public void setStreetAddress1(String value) { this .streetAddress1 = value; } /** * Gets the value of the streetAddress2 property. * * @return * possible object is * {@link String } * */ public String getStreetAddress2() { return streetAddress2; } /** * Sets the value of the streetAddress2 property. * * @param value * allowed object is * {@link String } * */ public void setStreetAddress2(String value) { this .streetAddress2 = value; } /** * Gets the value of the city property. * * @return * possible object is * {@link String } * */ public String getCity() { return city; } /** * Sets the value of the city property. * * @param value * allowed object is * {@link String } * */ public void setCity(String value) { this .city = value; } /** * Gets the value of the state property. * * @return * possible object is * {@link String } * */ public String getState() { return state; } /** * Sets the value of the state property. * * @param value * allowed object is * {@link String } * */ public void setState(String value) { this .state = value; } /** * Gets the value of the zipcode property. * * @return * possible object is * {@link String } * */ public String getZipcode() { return zipcode; } /** * Sets the value of the zipcode property. * * @param value * allowed object is * {@link String } * */ public void setZipcode(String value) { this .zipcode = value; } } |
Генерируемый JAXB PersonType.java
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
|
// // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4-2 // See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> // Any modifications to this file will be lost upon recompilation of the source schema. // Generated on: 2013.12.03 at 11:44:32 PM MST // package com.blogspot.marxsoftware; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; /** * <p>Java class for PersonType complex type. * * <p>The following schema fragment specifies the expected content contained within this class. * * <pre> * <complexType name="PersonType"> * <complexContent> * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> * <sequence> * <element name="MailingAddress" type="{http://marxsoftware.blogspot.com/}AddressType"/> * <element name="ResidentialAddress" type="{http://marxsoftware.blogspot.com/}AddressType" minOccurs="0"/> * </sequence> * <attribute name="firstName" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="lastName" type="{http://www.w3.org/2001/XMLSchema}string" /> * </restriction> * </complexContent> * </complexType> * </pre> * * */ @XmlAccessorType (XmlAccessType.FIELD) @XmlType (name = "PersonType" , propOrder = { "mailingAddress" , "residentialAddress" }) public class PersonType { @XmlElement (name = "MailingAddress" , required = true ) protected AddressType mailingAddress; @XmlElement (name = "ResidentialAddress" ) protected AddressType residentialAddress; @XmlAttribute (name = "firstName" ) protected String firstName; @XmlAttribute (name = "lastName" ) protected String lastName; /** * Gets the value of the mailingAddress property. * * @return * possible object is * {@link AddressType } * */ public AddressType getMailingAddress() { return mailingAddress; } /** * Sets the value of the mailingAddress property. * * @param value * allowed object is * {@link AddressType } * */ public void setMailingAddress(AddressType value) { this .mailingAddress = value; } /** * Gets the value of the residentialAddress property. * * @return * possible object is * {@link AddressType } * */ public AddressType getResidentialAddress() { return residentialAddress; } /** * Sets the value of the residentialAddress property. * * @param value * allowed object is * {@link AddressType } * */ public void setResidentialAddress(AddressType value) { this .residentialAddress = value; } /** * Gets the value of the firstName property. * * @return * possible object is * {@link String } * */ public String getFirstName() { return firstName; } /** * Sets the value of the firstName property. * * @param value * allowed object is * {@link String } * */ public void setFirstName(String value) { this .firstName = value; } /** * Gets the value of the lastName property. * * @return * possible object is * {@link String } * */ public String getLastName() { return lastName; } /** * Sets the value of the lastName property. * * @param value * allowed object is * {@link String } * */ public void setLastName(String value) { this .lastName = value; } } |
Домен / Бизнес класс Address.java
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
|
package dustin.examples.orikademo; import java.util.Objects; /** * Address class. * * @author Dustin */ public class Address { private String streetAddress1; private String streetAddress2; private String municipality; private String state; private String zipCode; public Address() {} public Address( final String newStreetAddress1, final String newStreetAddress2, final String newMunicipality, final String newState, final String newZipCode) { this .streetAddress1 = newStreetAddress1; this .streetAddress2 = newStreetAddress2; this .municipality = newMunicipality; this .state = newState; this .zipCode = newZipCode; } public String getStreetAddress1() { return this .streetAddress1; } public void setStreetAddress1(String streetAddress1) { this .streetAddress1 = streetAddress1; } public String getStreetAddress2() { return this .streetAddress2; } public void setStreetAddress2(String streetAddress2) { this .streetAddress2 = streetAddress2; } public String getMunicipality() { return this .municipality; } public void setMunicipality(String municipality) { this .municipality = municipality; } public String getState() { return this .state; } public void setState(String state) { this .state = state; } public String getZipCode() { return this .zipCode; } public void setZipCode(String zipCode) { this .zipCode = zipCode; } @Override public int hashCode() { return Objects.hash( this .streetAddress1, this .streetAddress2, this .municipality, this .state, this .zipCode); } @Override public boolean equals(Object obj) { if (obj == null ) { return false ; } if (getClass() != obj.getClass()) { return false ; } final Address other = (Address) obj; if (!Objects.equals( this .streetAddress1, other.streetAddress1)) { return false ; } if (!Objects.equals( this .streetAddress2, other.streetAddress2)) { return false ; } if (!Objects.equals( this .municipality, other.municipality)) { return false ; } if (!Objects.equals( this .state, other.state)) { return false ; } if (!Objects.equals( this .zipCode, other.zipCode)) { return false ; } return true ; } @Override public String toString() { return "Address{" + "streetAddress1=" + streetAddress1 + ", streetAddress2=" + streetAddress2 + ", municipality=" + municipality + ", state=" + state + ", zipCode=" + zipCode + '}' ; } } |
Домен / Бизнес класс Person.java
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
|
package dustin.examples.orikademo; import java.util.Objects; /** * Person class. * * @author Dustin */ public class Person { private String lastName; private String firstName; private Address mailingAddress; private Address residentialAddress; public Person() {} public Person( final String newLastName, final String newFirstName, final Address newResidentialAddress, final Address newMailingAddress) { this .lastName = newLastName; this .firstName = newFirstName; this .residentialAddress = newResidentialAddress; this .mailingAddress = newMailingAddress; } public String getLastName() { return this .lastName; } public void setLastName(String lastName) { this .lastName = lastName; } public String getFirstName() { return this .firstName; } public void setFirstName(String firstName) { this .firstName = firstName; } public Address getMailingAddress() { return this .mailingAddress; } public void setMailingAddress(Address mailingAddress) { this .mailingAddress = mailingAddress; } public Address getResidentialAddress() { return this .residentialAddress; } public void setResidentialAddress(Address residentialAddress) { this .residentialAddress = residentialAddress; } @Override public int hashCode() { int hash = 3 ; hash = 19 * hash + Objects.hashCode( this .lastName); hash = 19 * hash + Objects.hashCode( this .firstName); hash = 19 * hash + Objects.hashCode( this .mailingAddress); hash = 19 * hash + Objects.hashCode( this .residentialAddress); return hash; } @Override public boolean equals(Object obj) { if (obj == null ) { return false ; } if (getClass() != obj.getClass()) { return false ; } final Person other = (Person) obj; if (!Objects.equals( this .lastName, other.lastName)) { return false ; } if (!Objects.equals( this .firstName, other.firstName)) { return false ; } if (!Objects.equals( this .mailingAddress, other.mailingAddress)) { return false ; } if (!Objects.equals( this .residentialAddress, other.residentialAddress)) { return false ; } return true ; } @Override public String toString() { return "Person{" + "lastName=" + lastName + ", firstName=" + firstName + ", mailingAddress=" + mailingAddress + ", residentialAddress=" + residentialAddress + '}' ; } } |
Как и в случае с Dozer, отображаемые классы должны иметь конструкторы без аргументов и методы «set» и «get» для поддержки преобразования в обоих направлениях без какой-либо специальной дополнительной настройки. Кроме того, как и в случае с Dozer, Orika автоматически сопоставляет поля с одинаковыми именами и упрощает настройку сопоставления исключений (полей, имена которых не совпадают). Следующий листинг кода для класса, который я называю OrikaPersonConverter
, демонстрирует создание и настройку Orika MapperFactory для сопоставления большинства полей по умолчанию и сопоставления полей с именами, отличающимися друг от друга («муниципалитет» и «город»), посредством явного сопоставления конфигурации. После настройки MapperFactory копирование из одного объекта в другой становится простым, и оба направления отображаются в методах copyPersonTypeFromPerson
и copyPersonFromPersonType
.
OrikaPersonConverter
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
|
package dustin.examples.orikademo; import com.blogspot.marxsoftware.AddressType; import com.blogspot.marxsoftware.PersonType; import ma.glasnost.orika.MapperFacade; import ma.glasnost.orika.MapperFactory; import ma.glasnost.orika.impl.DefaultMapperFactory; /** * Convert between instances of {@link com.blogspot.marxsoftware.PersonType} * and {@link dustin.examples.orikademo.Person}. * * @author Dustin */ public class OrikaPersonConverter { /** Orika Mapper Facade. */ private final static MapperFacade mapper; static { final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); mapperFactory.classMap(Address. class , AddressType. class ) .field( "municipality" , "city" ) .byDefault() .register(); mapper = mapperFactory.getMapperFacade(); } /** No-arguments constructor. */ public OrikaPersonConverter() {} /** * Provide an instance of {@link com.blogspot.marxsoftware.PersonType} * that corresponds with provided {@link dustin.examples.orikademo.Person} as * mapped by Dozer Mapper. * * @param person Instance of {@link dustin.examples.orikademo.Person} from which * {@link com.blogspot.marxsoftware.PersonType} will be extracted. * @return Instance of {@link com.blogspot.marxsoftware.PersonType} that * is based on provided {@link dustin.examples.orikademo.Person} instance. */ public PersonType copyPersonTypeFromPerson( final Person person) { PersonType personType = mapper.map(person, PersonType. class ); return personType; } /** * Provide an instance of {@link dustin.examples.orikademo.Person} that corresponds * with the provided {@link com.blogspot.marxsoftware.PersonType} as * mapped by Dozer Mapper. * * @param personType Instance of {@link com.blogspot.marxsoftware.PersonType} * from which {@link dustin.examples.orikademo.Person} will be extracted. * @return Instance of {@link dustin.examples.orikademo.Person} that is based on the * provided {@link com.blogspot.marxsoftware.PersonType}. */ public Person copyPersonFromPersonType( final PersonType personType) { Person person = mapper.map(personType, Person. class ); return person; } } |
Как и в случае с Dozer, отображение между двумя классами является двунаправленным, поэтому его необходимо выполнить только один раз, и оно будет применяться при копировании с одного объекта на другой.
Вывод
Как и Dozer, Orika предлагает гораздо больше возможностей для настройки и гибкости, чем продемонстрировано в этом посте. Однако для относительно простых отображений (которые очень распространены в приложениях, использующих объекты, сгенерированные JAXB), Orika очень проста в использовании из коробки. Хорошим ресурсом для получения дополнительной информации о Orika является Руководство пользователя Orika .