Статьи

Dozer: сопоставление объектов JAXB с объектами бизнеса / домена

Dozer – это программа с открытым исходным кодом ( лицензия Apache 2 ) «Java Bean to Java Bean mapper, которая рекурсивно копирует данные из одного объекта в другой». Как следует из этого описания из его главной веб-страницы, оно используется для сопоставления двух экземпляров JavaBeans для автоматического копирования данных между экземплярами. Хотя это может быть любой из многих типов экземпляров JavaBeans, я сосредоточусь на этом посте на использовании Dozer для сопоставления сгенерированных JAXB объектов с «объектами бизнес-данных» (иногда называемыми «объектами домена»).

В приложениях Java, использующих архитектуру Java для привязки XML ( JAXB ), разработчики очень часто пишут конкретные бизнес-объекты или доменные объекты для использования в самом приложении и используют только объекты, сгенерированные JAXB, для чтения (демаршаллинга) и записи (маршаллинг) XML. Хотя использование объектов, сгенерированных JAXB, в качестве объектов бизнеса / домена имеет некоторую привлекательность ( DRY ), у этого подхода есть недостатки. Сгенерированные JAXB классы не имеют реализаций toString () , equals (Object) или hashCode () , что делает эти сгенерированные классы непригодными для использования во многих типах коллекций, непригодными для сравнения, отличного от сравнения идентификаторов, и непригодными для простой регистрации их содержимого. , Редактирование этих сгенерированных классов вручную после их генерации утомительно и не способствует регенерации классов JAXB снова, когда в исходный XSD могут быть внесены даже небольшие изменения.

Хотя основы JAXB2 можно использовать для обеспечения того, чтобы сгенерированные JAXB классы имели некоторые общие методы, необходимые для использования в коллекциях, для сравнения и для регистрации их содержимого, потенциально еще большая проблема с использованием сгенерированных JAXB классов в качестве домена / это влечет за собой тесную связь бизнес-логики с XSD. Изменение схемы в XSD (например, для обновления версии) обычно приводит к другой структуре пакета для классов, сгенерированных из этого XSD через JAXB. Затем другая структура пакета заставляет весь код, который импортирует эти сгенерированные JAXB классы, изменять свои операторы импорта. Изменения содержимого в XSD могут иметь еще более существенные последствия, влияя на методы get / set для классов JAXB, которые будут разбросаны по всему приложению, если классы JAXB используются для доменных / бизнес-объектов.

Предполагая, что кто-то решает не использовать сгенерированные JAXB классы в качестве бизнес-классов / классов доменов, существует несколько способов сопоставить сгенерированные JAXB-классы с классами, определяющими бизнес-объекты / доменные объекты, через «уровень отображения», описанный в коде или конфигурации. Чтобы продемонстрировать две реализации уровня отображения на основе кода и продемонстрировать уровень отображения на основе Dozer, я привожу несколько простых примеров сгенерированных JAXB классов и пользовательских классов бизнеса / домена.

Первая часть примера для этого поста – XSD, из которого JAXB’x xjc будет инструктироваться общим классам для маршалинга в XML, описанного этим XSD, или демаршаллинга из XML, описанного этим XSD. XSD, который показан далее, определяет элемент Person который может иметь вложенные MailingAddress и ResidentialAddress и два атрибута String для имени и фамилии. Также обратите внимание, что основным пространством имен является http://marxsoftware.blogspot.com/, которое JAXB будет использовать для определения иерархии пакетов Java для классов, сгенерированных из этого XSD.

Person.xsd

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
<?xml version="1.0"?>
<xs:schema version="1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:marx="http://marxsoftware.blogspot.com/"
           targetNamespace="http://marxsoftware.blogspot.com/"
           elementFormDefault="qualified">
    
   <xs:element name="Person" type="marx:PersonType" />
    
   <xs:complexType name="PersonType">
      <xs:sequence>
         <xs:element name="MailingAddress" type="marx:AddressType" />
         <xs:element name="ResidentialAddress" type="marx:AddressType" minOccurs="0" />
      </xs:sequence>
      <xs:attribute name="firstName" type="xs:string" />
      <xs:attribute name="lastName" type="xs:string" />
   </xs:complexType>
    
   <xs:complexType name="AddressType">
      <xs:attribute name="streetAddress1" type="xs:string" use="required" />
      <xs:attribute name="streetAddress2" type="xs:string" use="optional" />
      <xs:attribute name="city" type="xs:string" use="required" />
      <xs:attribute name="state" type="xs:string" use="required" />
      <xs:attribute name="zipcode" type="xs:string" use="required" />
   </xs:complexType>
    
</xs:schema>

Когда xjc (JAXB-компилятор, поставляемый с JDK Oracle) выполняется для вышеуказанного XSD, следующие четыре класса создаются в каталоге com / blogspot / marxsoftware (производном от пространства имен XSD): AddressType.java , PersonType.java , ObjectFactory.java , и package-info.java .

jaxbGeneratedClassesPersonAddress

Следующие два списка кода относятся к двум основным интересующим классам ( PersonType.java и AddressType.java ), сгенерированным JAXB. Основная цель показать их здесь – это напоминание о том, что у них нет методов, которые нам часто нужны для классов нашего бизнеса / домена.

Генерируемый 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
151
152
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4-2
// 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;
    }
 
}

Генерируемый 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
172
173
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4-2
// 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, и объектами бизнес / доменов, написанными на заказ, состоит в том, чтобы использовать методы «get» одного объекта и передавать его возвращаемое значение методу «set» другого объекта. Например, в процессе демаршаллинга / считывания XML в приложение результаты методов «get», вызываемых для объектов, сгенерированных JAXB, могут быть переданы в методы «set» объектов business / domain. В противоположном направлении маршаллинг / запись XML может быть легко осуществлена ​​путем передачи результата методов get для домена / бизнес-объектов соответствующим методам «set» сгенерированных JAXB объектов. Следующий листинг кода для PersonCoverter.java и иллюстрирует одну реализацию этого подхода.

PersonConverter.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
package dustin.examples.dozerdemo;
 
import com.blogspot.marxsoftware.AddressType;
import com.blogspot.marxsoftware.ObjectFactory;
import com.blogspot.marxsoftware.PersonType;
import dustin.examples.Address;
import dustin.examples.Person;
 
/**
 * Static functions for converting between JAXB-generated objects and domain
 * objects.
 *
 * @author Dustin
 */
public class PersonConverter
{
   /**
    * Extract business object {@link dustin.examples.Person} from the JAXB
    * generated object {@link com.blogspot.marxsoftware.PersonType}.
    *
    * @param personType JAXB-generated {@link com.blogspot.marxsoftware.PersonType}
    *    from which to extract {@link dustin.examples.Person} object.
    * @return Instance of {@link dustin.examples.Person} based on the provided
    *    {@link com.blogspot.marxsoftware.PersonType}.
    */
   public static Person extractPersonFromPersonType(final PersonType personType)
   {
      final String lastName = personType.getLastName();
      final String firstName = personType.getFirstName();
      final Address residentialAddress =
         extractAddressFromAddressType(personType.getResidentialAddress());
      final Address mailingAddress =
         extractAddressFromAddressType(personType.getMailingAddress());
      return new Person(lastName, firstName, residentialAddress, mailingAddress);
   }
 
   /**
    * Extract business object {@link dustin.examples.Address} from the JAXB
    * generated object {@link com.blogspot.marxsoftware.AddressType}.
    *
    * @param addressType JAXB-generated {@link com.blogspot.marxsoftware.AddressType}
    *    from which to extract {@link dustin.examples.Address} object.
    * @return Instance of {@link dustin.examples.Address} based on the provided
    *    {@link com.blogspot.marxsoftware.AddressType}.
    */
   public static Address extractAddressFromAddressType(final AddressType addressType)
   {
      return new Address(
         addressType.getStreetAddress1(), addressType.getStreetAddress2(),
         addressType.getCity(), addressType.getState(), addressType.getZipcode());
   }
 
   /**
    * Extract an instance of {@link com.blogspot.marxsoftware.PersonType} from
    * an instance of {@link dustin.examples.Person}.
    *
    * @param person Instance of {@link dustin.examples.Person} from which
    *    instance of JAXB-generated {@link com.blogspot.marxsoftware.PersonType}
    *    is desired.
    * @return Instance of {@link com.blogspot.marxsoftware.PersonType} based on
    *    provided instance of {@link dustin.examples.Person}.
    */
   public static PersonType extractPersonTypeFromPerson(final Person person)
   {
      final ObjectFactory objectFactory = new ObjectFactory();
      final AddressType residentialAddressType =
         extractAddressTypeFromAddress(person.getResidentialAddress());
      final AddressType mailingAddressType =
         extractAddressTypeFromAddress(person.getMailingAddress());
       
      final PersonType personType = objectFactory.createPersonType();
      personType.setLastName(person.getLastName());
      personType.setFirstName(person.getFirstName());
      personType.setResidentialAddress(residentialAddressType);
      personType.setMailingAddress(mailingAddressType);
       
      return personType;
   }
 
   /**
    * Extract an instance of {@link com.blogspot.marxsoftware.AddressType} from
    * an instance of {@link dustin.examples.Address}.
    *
    * @param address Instance of {@link dustin.examples.Address} from which
    *    instance of JAXB-generated {@link com.blogspot.marxsoftware.AddressType}
    *    is desired.
    * @return Instance of {@link com.blogspot.marxsoftware.AddressType} based on
    *    provided instance of {@link dustin.examples.Address}.
    */
   public static AddressType extractAddressTypeFromAddress(final Address address)
   {
      final ObjectFactory objectFactory = new ObjectFactory();
      final AddressType addressType = objectFactory.createAddressType();
      addressType.setStreetAddress1(address.getStreetAddress1());
      addressType.setStreetAddress2(address.getStreetAddress2());
      addressType.setCity(address.getMunicipality());
      addressType.setState(address.getState());
      addressType.setZipcode(address.getZipCode());
      return addressType;
   }
}

В последнем листинге кода продемонстрирован общий подход сторонних классов к копированию данных в обоих направлениях между объектами, сгенерированными JAXB, и объектами домена / бизнеса. Другой подход заключается в том, чтобы встроить эту возможность копирования в домен / бизнес-объекты. Это показано в следующих двух листингах кода для PersonPlus.java и AddressPlus.java которые являются версиями ранее Person.java и Person.java с добавленной поддержкой для копирования данных в и из объектов, сгенерированных JAXB. Для удобства я добавил новые методы в конец классов после реализаций toString .

PersonPlus.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
package dustin.examples;
 
import com.blogspot.marxsoftware.ObjectFactory;
import com.blogspot.marxsoftware.PersonType;
import java.util.Objects;
 
/**
 * Person class enhanced to support copying to/from JAXB-generated PersonType.
 *
 * @author Dustin
 */
public class PersonPlus
{
   private String lastName;
   private String firstName;
   private AddressPlus mailingAddress;
   private AddressPlus residentialAddress;
 
   public PersonPlus(
      final String newLastName,
      final String newFirstName,
      final AddressPlus newResidentialAddress,
      final AddressPlus 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 AddressPlus getMailingAddress()
   {
      return this.mailingAddress;
   }
 
   public void setMailingAddress(AddressPlus mailingAddress)
   {
      this.mailingAddress = mailingAddress;
   }
 
   public AddressPlus getResidentialAddress()
   {
      return this.residentialAddress;
   }
 
   public void setResidentialAddress(AddressPlus 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 PersonPlus other = (PersonPlus) 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  "PersonPlus{" + "lastName=" + lastName + ", firstName=" + firstName
            + ", mailingAddress=" + mailingAddress + ", residentialAddress="
            + residentialAddress + '}';
   }
    
   /**
    * Provide a JAXB-generated instance of {@link com.blogspot.marxsoftware.PersonType}
    * that corresponds to me.
    *
    * @return Instance of {@link com.blogspot.marxsoftware.PersonType} that
    *    corresponds to me.
    */
   public PersonType toPersonType()
   {
      final ObjectFactory objectFactory = new ObjectFactory();
      final PersonType personType = objectFactory.createPersonType();
      personType.setFirstName(this.firstName);
      personType.setLastName(this.lastName);
      personType.setResidentialAddress(this.residentialAddress.toAddressType());
      personType.setMailingAddress(this.mailingAddress.toAddressType());
      return personType;
   }
 
   /**
    * Provide instance of {@link dustin.examples.PersonPlus} corresponding
    * to the provided instance of JAXB-generated object
    * {@link com.blogspot.marxsoftware.PersonType}.
    *
    * @param personType Instance of JAXB-generated object
    *    {@link com.blogspot.marxsoftware.PersonType}.
    * @return Instance of me corresponding to provided JAXB-generated object
    *    {@link com.blogspot.marxsoftware.PersonType}.
    */
   public static PersonPlus fromPersonType(final PersonType personType)
   {
      final AddressPlus residentialAddress =
         AddressPlus.fromAddressType(personType.getResidentialAddress());
      final AddressPlus mailingAddress =
         AddressPlus.fromAddressType(personType.getMailingAddress());
      return new PersonPlus(personType.getLastName(), personType.getFirstName(),
                            residentialAddress, mailingAddress);
   }
}

AddressPlus.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
package dustin.examples;
 
import com.blogspot.marxsoftware.AddressType;
import com.blogspot.marxsoftware.ObjectFactory;
import java.util.Objects;
 
/**
 * Address class with support for copying to/from JAXB-generated class
 * {@link com.blogspot.marxsoftware.AddressType}.
 *
 * @author Dustin
 */
public class AddressPlus
{
   private String streetAddress1;
   private String streetAddress2;
   private String municipality;
   private String state;
   private String zipCode;
 
   public AddressPlus(
      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 AddressPlus other = (AddressPlus) 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 + '}';
   }
 
    /**
    * Provide a JAXB-generated instance of {@link com.blogspot.marxsoftware.AddressType}
    * that corresponds to an instance of me.
    *
    * @return Instance of JAXB-generated {@link com.blogspot.marxsoftware.AddressType}
    *    that corresponds to me.
    */
   public AddressType toAddressType()
   {
      final ObjectFactory objectFactory = new ObjectFactory();
      final AddressType addressType = objectFactory.createAddressType();
      addressType.setStreetAddress1(this.streetAddress1);
      addressType.setStreetAddress2(this.streetAddress2);
      addressType.setCity(this.municipality);
      addressType.setState(this.state);
      addressType.setZipcode(this.zipCode);
      return addressType;
   }
 
   /**
    * Provide instance of {@link dustin.examples.AddressPlus} corresponding
    * to the provided instance of JAXB-generated object
    * {@link com.blogspot.marxsoftware.AddressType}.
    *
    * @param addressType Instance of JAXB-generated object
    *    {@link com.blogspot.marxsoftware.AddressType}.
    * @return Instance of me corresponding to provided JAXB-generated object
    *    {@link com.blogspot.marxsoftware.AddressType}.
    */
   public static AddressPlus fromAddressType(final AddressType addressType)
   {
      return new AddressPlus(
         addressType.getStreetAddress1(),
         addressType.getStreetAddress2(),
         addressType.getCity(),
         addressType.getState(),
         addressType.getZipcode());
   }
}

Два подхода, продемонстрированных выше для сопоставления сгенерированных JAXB объектов с объектами бизнеса / домена, безусловно, будут работать, и для моего простого примера можно было бы рассмотреть наилучшие подходы к использованию (особенно учитывая, что NetBeans сделал генерацию объектов бизнеса / домена почти тривиальной). Однако для более значимых иерархий объектов, которые требуют сопоставления, сопоставление на основе конфигурации Dozer может считаться предпочтительным.

Dozer загружается со страницы загрузки (в данном случае dozer-5.3.2.jar ). Страница « Начало работы» показывает, что сопоставление действительно легко (минимальная конфигурация), когда атрибуты сопоставляемых классов имеют одинаковые имена. Это не тот случай в моем примере, в котором я намеренно сделал один атрибут «город», а другой – «муниципалитет», чтобы сделать отображение более интересным. Поскольку эти имена разные, мне нужно настроить отображение Dozer, и это делается с помощью конфигурации отображения XML. Необходимый файл сопоставления назван с именем сопоставления по умолчанию в dozerBeanMapping.xml и показан далее. Мне нужно было только сопоставить два поля с разными именами ( city и municipality ), потому что все остальные поля сопоставляемых двух классов имеют одинаковые имена и автоматически сопоставляются без явной настройки.

dozerBeanMapping.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://dozer.sourceforge.net
 
  <configuration>
    <stop-on-errors>true</stop-on-errors>
    <date-format>MM/dd/yyyy HH:mm:ss</date-format>
    <wildcard>true</wildcard>
  </configuration>
 
  <mapping>
    <class-a>dustin.examples.Address</class-a>
    <class-b>com.blogspot.marxsoftware.AddressType</class-b>
      <field>
        <a>municipality</a>
        <b>city</b>
      </field>
  </mapping
                    
</mappings>

Обратите внимание, что XML – не единственный подход, который можно использовать для настройки отображения Dozer; аннотации и программный API также поддерживаются.

Страница сторонних объектных фабрик Dozer кратко описывает использование Dozer с JAXB и использование JAXBBeanFactory . Также рекомендуется использовать инъекцию с Dozer, и приведен пример интеграции Spring . Для моего простого примера применения Dozer я не использую эти подходы, но использую очень простой подход к созданию экземпляров. Это показано в следующем листинге кода.

DozerPersonConverter.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
package dustin.examples.dozerdemo;
 
import com.blogspot.marxsoftware.PersonType;
import dustin.examples.Person;
import java.util.ArrayList;
import java.util.List;
import org.dozer.DozerBeanMapper;
 
/**
 * Dozer-based converter.
 *
 * @author Dustin
 */
public class DozerPersonConverter
{
   static final DozerBeanMapper mapper = new DozerBeanMapper();
    
   static
   {
      final List<String> mappingFilesNames = new ArrayList<>();
      mappingFilesNames.add("dozerBeanMapping.xml");
      mapper.setMappingFiles(mappingFilesNames);
   }
 
   /**
    * Provide an instance of {@link com.blogspot.marxsoftware.PersonType}
    * that corresponds with provided {@link dustin.examples.Person} as
    * mapped by Dozer Mapper.
    *
    * @param person Instance of {@link dustin.examples.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.Person} instance.
    */
   public PersonType copyPersonTypeFromPerson(final Person person)
   {
      final PersonType personType =
         mapper.map(person, PersonType.class);
      return personType;
   }
 
   /**
    * Provide an instance of {@link dustin.examples.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.Person} will be extracted.
    * @return Instance of {@link dustin.examples.Person} that is based on the
    *    provided {@link com.blogspot.marxsoftware.PersonType}.
    */
   public Person copyPersonFromPersonType(final PersonType personType)
   {
      final Person person =
         mapper.map(personType, Person.class);
      return person;
   }
}

В предыдущем примере показано, как мало кода требуется для сопоставления сгенерированных JAXB объектов с объектами бизнеса / домена. Конечно, нужен был какой-то XML, но только для полей с разными именами. Это означает, что чем больше отличаются имена полей, тем больше требуется конфигурации. Однако до тех пор, пока поля в основном отображаются один в один без какой-либо специальной логики «преобразования» между ними, Dozer заменяет большую часть утомительного кода отображением конфигурации.

Если необходимо преобразовать поля (например, преобразовать метры в одном объекте в километры в другом объекте), тогда эта поддержка отображения может быть менее привлекательной, когда необходимо написать пользовательские преобразователи . Dozer mapping также может стать более трудным для правильного применения с глубоко вложенными объектами, но мой пример действительно вложил Address в Person в качестве простого примера. Хотя сложные сопоставления могут стать менее привлекательными в Dozer, многие сопоставления сгенерированных JAXB объектов бизнес-объектам / доменам являются достаточно простыми сопоставлениями, которые хорошо обслуживает Dozer.

Последнее, на что я хотел бы обратить внимание в этом посте, – это то, что Dozer зависит от времени выполнения некоторых сторонних библиотек. К счастью, эти библиотеки в любом случае обычно используются в проектах Java и легко доступны. Как показывают следующие два изображения, необходимыми зависимостями времени выполнения являются SLF4J, Apache Commons Lang, Apache Commons Logging и Apache BeanUtils.

Страница Зависимости Dozer Runtime
dozerAdvertisedRuntimeDependencies
Библиотеки проектов NetBeans 7.4 для примеров этого поста
dozerNetBeansProjectRuntimeDependencies
Требуется небольшое усилие для настройки Dozer и его зависимостей, а затем для настройки отображений, но эти усилия могут быть хорошо вознаграждены значительно уменьшенным кодом сопоставления во многих распространенных приложениях JAXB-to-business для копирования данных объектов.