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 | <?xmlversion="1.0"?><xs:schemaversion="1.0"           elementFormDefault="qualified">      <xs:elementname="Person"type="marx:PersonType"/>      <xs:complexTypename="PersonType">      <xs:sequence>         <xs:elementname="MailingAddress"type="marx:AddressType"/>         <xs:elementname="ResidentialAddress"type="marx:AddressType"minOccurs="0"/>      </xs:sequence>      <xs:attributename="firstName"type="xs:string"/>      <xs:attributename="lastName"type="xs:string"/>   </xs:complexType>      <xs:complexTypename="AddressType">      <xs:attributename="streetAddress1"type="xs:string"use="required"/>      <xs:attributename="streetAddress2"type="xs:string"use="optional"/>      <xs:attributename="city"type="xs:string"use="required"/>      <xs:attributename="state"type="xs:string"use="required"/>      <xs:attributename="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 . 
  Следующие два списка кода относятся к двум основным интересующим классам ( 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 // See http://java.sun.com/xml/jaxb // 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 //packagecom.blogspot.marxsoftware;importjavax.xml.bind.annotation.XmlAccessType;importjavax.xml.bind.annotation.XmlAccessorType;importjavax.xml.bind.annotation.XmlAttribute;importjavax.xml.bind.annotation.XmlElement;importjavax.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"})publicclassPersonType {    @XmlElement(name = "MailingAddress", required = true)    protectedAddressType mailingAddress;    @XmlElement(name = "ResidentialAddress")    protectedAddressType residentialAddress;    @XmlAttribute(name = "firstName")    protectedString firstName;    @XmlAttribute(name = "lastName")    protectedString lastName;    /**     * Gets the value of the mailingAddress property.     *      * @return     *     possible object is     *     {@link AddressType }     *          */    publicAddressType getMailingAddress() {        returnmailingAddress;    }    /**     * Sets the value of the mailingAddress property.     *      * @param value     *     allowed object is     *     {@link AddressType }     *          */    publicvoidsetMailingAddress(AddressType value) {        this.mailingAddress = value;    }    /**     * Gets the value of the residentialAddress property.     *      * @return     *     possible object is     *     {@link AddressType }     *          */    publicAddressType getResidentialAddress() {        returnresidentialAddress;    }    /**     * Sets the value of the residentialAddress property.     *      * @param value     *     allowed object is     *     {@link AddressType }     *          */    publicvoidsetResidentialAddress(AddressType value) {        this.residentialAddress = value;    }    /**     * Gets the value of the firstName property.     *      * @return     *     possible object is     *     {@link String }     *          */    publicString getFirstName() {        returnfirstName;    }    /**     * Sets the value of the firstName property.     *      * @param value     *     allowed object is     *     {@link String }     *          */    publicvoidsetFirstName(String value) {        this.firstName = value;    }    /**     * Gets the value of the lastName property.     *      * @return     *     possible object is     *     {@link String }     *          */    publicString getLastName() {        returnlastName;    }    /**     * Sets the value of the lastName property.     *      * @param value     *     allowed object is     *     {@link String }     *          */    publicvoidsetLastName(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 // See http://java.sun.com/xml/jaxb // 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 //packagecom.blogspot.marxsoftware;importjavax.xml.bind.annotation.XmlAccessType;importjavax.xml.bind.annotation.XmlAccessorType;importjavax.xml.bind.annotation.XmlAttribute;importjavax.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")publicclassAddressType {    @XmlAttribute(name = "streetAddress1", required = true)    protectedString streetAddress1;    @XmlAttribute(name = "streetAddress2")    protectedString streetAddress2;    @XmlAttribute(name = "city", required = true)    protectedString city;    @XmlAttribute(name = "state", required = true)    protectedString state;    @XmlAttribute(name = "zipcode", required = true)    protectedString zipcode;    /**     * Gets the value of the streetAddress1 property.     *      * @return     *     possible object is     *     {@link String }     *          */    publicString getStreetAddress1() {        returnstreetAddress1;    }    /**     * Sets the value of the streetAddress1 property.     *      * @param value     *     allowed object is     *     {@link String }     *          */    publicvoidsetStreetAddress1(String value) {        this.streetAddress1 = value;    }    /**     * Gets the value of the streetAddress2 property.     *      * @return     *     possible object is     *     {@link String }     *          */    publicString getStreetAddress2() {        returnstreetAddress2;    }    /**     * Sets the value of the streetAddress2 property.     *      * @param value     *     allowed object is     *     {@link String }     *          */    publicvoidsetStreetAddress2(String value) {        this.streetAddress2 = value;    }    /**     * Gets the value of the city property.     *      * @return     *     possible object is     *     {@link String }     *          */    publicString getCity() {        returncity;    }    /**     * Sets the value of the city property.     *      * @param value     *     allowed object is     *     {@link String }     *          */    publicvoidsetCity(String value) {        this.city = value;    }    /**     * Gets the value of the state property.     *      * @return     *     possible object is     *     {@link String }     *          */    publicString getState() {        returnstate;    }    /**     * Sets the value of the state property.     *      * @param value     *     allowed object is     *     {@link String }     *          */    publicvoidsetState(String value) {        this.state = value;    }    /**     * Gets the value of the zipcode property.     *      * @return     *     possible object is     *     {@link String }     *          */    publicString getZipcode() {        returnzipcode;    }    /**     * Sets the value of the zipcode property.     *      * @param value     *     allowed object is     *     {@link String }     *          */    publicvoidsetZipcode(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 | packagedustin.examples.dozerdemo;importcom.blogspot.marxsoftware.AddressType;importcom.blogspot.marxsoftware.ObjectFactory;importcom.blogspot.marxsoftware.PersonType;importdustin.examples.Address;importdustin.examples.Person;/** * Static functions for converting between JAXB-generated objects and domain * objects. *  * @author Dustin */publicclassPersonConverter{   /**    * 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}.    */   publicstaticPerson extractPersonFromPersonType(finalPersonType personType)   {      finalString lastName = personType.getLastName();      finalString firstName = personType.getFirstName();      finalAddress residentialAddress =         extractAddressFromAddressType(personType.getResidentialAddress());      finalAddress mailingAddress =         extractAddressFromAddressType(personType.getMailingAddress());      returnnewPerson(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}.    */   publicstaticAddress extractAddressFromAddressType(finalAddressType addressType)   {      returnnewAddress(         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}.    */   publicstaticPersonType extractPersonTypeFromPerson(finalPerson person)   {      finalObjectFactory objectFactory = newObjectFactory();      finalAddressType residentialAddressType =         extractAddressTypeFromAddress(person.getResidentialAddress());      finalAddressType mailingAddressType =         extractAddressTypeFromAddress(person.getMailingAddress());            finalPersonType personType = objectFactory.createPersonType();      personType.setLastName(person.getLastName());      personType.setFirstName(person.getFirstName());      personType.setResidentialAddress(residentialAddressType);      personType.setMailingAddress(mailingAddressType);            returnpersonType;   }   /**    * 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}.    */   publicstaticAddressType extractAddressTypeFromAddress(finalAddress address)   {      finalObjectFactory objectFactory = newObjectFactory();      finalAddressType addressType = objectFactory.createAddressType();      addressType.setStreetAddress1(address.getStreetAddress1());      addressType.setStreetAddress2(address.getStreetAddress2());      addressType.setCity(address.getMunicipality());      addressType.setState(address.getState());      addressType.setZipcode(address.getZipCode());      returnaddressType;   }} | 
  В последнем листинге кода продемонстрирован общий подход сторонних классов к копированию данных в обоих направлениях между объектами, сгенерированными 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 | packagedustin.examples;importcom.blogspot.marxsoftware.ObjectFactory;importcom.blogspot.marxsoftware.PersonType;importjava.util.Objects;/** * Person class enhanced to support copying to/from JAXB-generated PersonType. *  * @author Dustin */publicclassPersonPlus{   privateString lastName;   privateString firstName;   privateAddressPlus mailingAddress;   privateAddressPlus residentialAddress;   publicPersonPlus(      finalString newLastName,      finalString newFirstName,      finalAddressPlus newResidentialAddress,      finalAddressPlus newMailingAddress)   {      this.lastName = newLastName;      this.firstName = newFirstName;      this.residentialAddress = newResidentialAddress;      this.mailingAddress = newMailingAddress;   }   publicString getLastName()   {      returnthis.lastName;   }   publicvoidsetLastName(String lastName) {      this.lastName = lastName;   }   publicString getFirstName()   {      returnthis.firstName;   }      publicvoidsetFirstName(String firstName)   {      this.firstName = firstName;   }      publicAddressPlus getMailingAddress()   {      returnthis.mailingAddress;   }   publicvoidsetMailingAddress(AddressPlus mailingAddress)   {      this.mailingAddress = mailingAddress;   }   publicAddressPlus getResidentialAddress()   {      returnthis.residentialAddress;   }   publicvoidsetResidentialAddress(AddressPlus residentialAddress)   {      this.residentialAddress = residentialAddress;   }   @Override   publicinthashCode()   {      inthash = 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);      returnhash;   }   @Override   publicbooleanequals(Object obj)   {      if(obj == null)      {         returnfalse;      }      if(getClass() != obj.getClass())      {         returnfalse;      }      finalPersonPlus other = (PersonPlus) obj;      if(!Objects.equals(this.lastName, other.lastName))      {         returnfalse;      }      if(!Objects.equals(this.firstName, other.firstName))      {         returnfalse;      }      if(!Objects.equals(this.mailingAddress, other.mailingAddress))      {         returnfalse;      }      if(!Objects.equals(this.residentialAddress, other.residentialAddress))      {         returnfalse;      }      returntrue;   }   @Override   publicString 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.    */   publicPersonType toPersonType()   {      finalObjectFactory objectFactory = newObjectFactory();      finalPersonType personType = objectFactory.createPersonType();      personType.setFirstName(this.firstName);      personType.setLastName(this.lastName);      personType.setResidentialAddress(this.residentialAddress.toAddressType());      personType.setMailingAddress(this.mailingAddress.toAddressType());      returnpersonType;   }   /**    * 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}.    */   publicstaticPersonPlus fromPersonType(finalPersonType personType)   {      finalAddressPlus residentialAddress =         AddressPlus.fromAddressType(personType.getResidentialAddress());      finalAddressPlus mailingAddress =         AddressPlus.fromAddressType(personType.getMailingAddress());      returnnewPersonPlus(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 | packagedustin.examples;importcom.blogspot.marxsoftware.AddressType;importcom.blogspot.marxsoftware.ObjectFactory;importjava.util.Objects;/** * Address class with support for copying to/from JAXB-generated class * {@link com.blogspot.marxsoftware.AddressType}. *  * @author Dustin */publicclassAddressPlus{   privateString streetAddress1;   privateString streetAddress2;   privateString municipality;   privateString state;   privateString zipCode;   publicAddressPlus(      finalString newStreetAddress1,      finalString newStreetAddress2,      finalString newMunicipality,      finalString newState,      finalString newZipCode)   {      this.streetAddress1 = newStreetAddress1;      this.streetAddress2 = newStreetAddress2;      this.municipality = newMunicipality;      this.state = newState;      this.zipCode = newZipCode;   }   publicString getStreetAddress1()   {      returnthis.streetAddress1;   }   publicvoidsetStreetAddress1(String streetAddress1)   {      this.streetAddress1 = streetAddress1;   }   publicString getStreetAddress2()   {      returnthis.streetAddress2;   }   publicvoidsetStreetAddress2(String streetAddress2)   {      this.streetAddress2 = streetAddress2;   }   publicString getMunicipality()   {      returnthis.municipality;   }   publicvoidsetMunicipality(String municipality)   {      this.municipality = municipality;   }   publicString getState() {      returnthis.state;   }   publicvoidsetState(String state)   {      this.state = state;   }   publicString getZipCode()    {      returnthis.zipCode;   }   publicvoidsetZipCode(String zipCode)   {      this.zipCode = zipCode;   }   @Override   publicinthashCode()   {      returnObjects.hash(         this.streetAddress1, this.streetAddress2, this.municipality,         this.state, this.zipCode);   }   @Override   publicbooleanequals(Object obj)   {      if(obj == null) {         returnfalse;      }      if(getClass() != obj.getClass()) {         returnfalse;      }      finalAddressPlus other = (AddressPlus) obj;      if(!Objects.equals(this.streetAddress1, other.streetAddress1))      {         returnfalse;      }      if(!Objects.equals(this.streetAddress2, other.streetAddress2))      {         returnfalse;      }      if(!Objects.equals(this.municipality, other.municipality))      {         returnfalse;      }      if(!Objects.equals(this.state, other.state))      {         returnfalse;      }      if(!Objects.equals(this.zipCode, other.zipCode))      {         returnfalse;      }      returntrue;   }   @Override   publicString 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.    */   publicAddressType toAddressType()   {      finalObjectFactory objectFactory = newObjectFactory();      finalAddressType addressType = objectFactory.createAddressType();      addressType.setStreetAddress1(this.streetAddress1);      addressType.setStreetAddress2(this.streetAddress2);      addressType.setCity(this.municipality);      addressType.setState(this.state);      addressType.setZipcode(this.zipCode);      returnaddressType;   }   /**    * 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}.    */   publicstaticAddressPlus fromAddressType(finalAddressType addressType)   {      returnnewAddressPlus(         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 | <?xmlversion="1.0"encoding="UTF-8"?>          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 | packagedustin.examples.dozerdemo;importcom.blogspot.marxsoftware.PersonType;importdustin.examples.Person;importjava.util.ArrayList;importjava.util.List;importorg.dozer.DozerBeanMapper;/** * Dozer-based converter. *  * @author Dustin */publicclassDozerPersonConverter{   staticfinalDozerBeanMapper mapper = newDozerBeanMapper();      static   {      finalList<String> mappingFilesNames = newArrayList<>();      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.    */   publicPersonType copyPersonTypeFromPerson(finalPerson person)   {      finalPersonType personType =          mapper.map(person, PersonType.class);      returnpersonType;   }   /**    * 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}.    */   publicPerson copyPersonFromPersonType(finalPersonType personType)   {      finalPerson person =          mapper.map(personType, Person.class);      returnperson;   }} | 
В предыдущем примере показано, как мало кода требуется для сопоставления сгенерированных JAXB объектов с объектами бизнеса / домена. Конечно, нужен был какой-то XML, но только для полей с разными именами. Это означает, что чем больше отличаются имена полей, тем больше требуется конфигурации. Однако до тех пор, пока поля в основном отображаются один в один без какой-либо специальной логики «преобразования» между ними, Dozer заменяет большую часть утомительного кода отображением конфигурации.
  Если необходимо преобразовать поля (например, преобразовать метры в одном объекте в километры в другом объекте), тогда эта поддержка отображения может быть менее привлекательной, когда необходимо написать пользовательские преобразователи .  Dozer mapping также может стать более трудным для правильного применения с глубоко вложенными объектами, но мой пример действительно вложил Address в Person в качестве простого примера.  Хотя сложные сопоставления могут стать менее привлекательными в Dozer, многие сопоставления сгенерированных JAXB объектов бизнес-объектам / доменам являются достаточно простыми сопоставлениями, которые хорошо обслуживает Dozer. 
Последнее, на что я хотел бы обратить внимание в этом посте, — это то, что Dozer зависит от времени выполнения некоторых сторонних библиотек. К счастью, эти библиотеки в любом случае обычно используются в проектах Java и легко доступны. Как показывают следующие два изображения, необходимыми зависимостями времени выполнения являются SLF4J, Apache Commons Lang, Apache Commons Logging и Apache BeanUtils.
  Страница Зависимости Dozer Runtime 
 
  Библиотеки проектов NetBeans 7.4 для примеров этого поста 
 
  Требуется небольшое усилие для настройки Dozer и его зависимостей, а затем для настройки отображений, но эти усилия могут быть хорошо вознаграждены значительно уменьшенным кодом сопоставления во многих распространенных приложениях JAXB-to-business для копирования данных объектов. 
