Dozer掃盲級教程

OkidoGreen發表於2020-04-05

https://blog.csdn.net/stu059074244/article/details/77767636

前言
這篇文章是本人在閱讀Dozer官方文件(5.5.1版本,官網已經一年多沒更新了)的過程中,整理下來我認為比較基礎的應用場景。
本文中提到的例子應該能覆蓋JavaBean對映的大部分場景,希望對你有所幫助。

概述
Dozer是什麼?
Dozer是一個JavaBean對映工具庫。

它支援簡單的屬性對映,複雜型別對映,雙向對映,隱式顯式的對映,以及遞迴對映。
它支援三種對映方式:註解、API、XML。
它是開源的,遵從Apache 2.0 協議
Dozer原始碼地址
Dozer官方文件

安裝
引入jar包
maven方式
如果你的專案使用maven,新增以下依賴到你的pom.xml即可:

<dependency>
    <groupId>net.sf.dozer</groupId>
    <artifactId>dozer</artifactId>
    <version>5.4.0</version>
</dependency>


非maven方式
如果你的專案不使用maven,那就只能發揚不怕苦不怕累的精神了。
使用Dozer需要引入Dozer的jar包以及其依賴的第三方jar包。
Dozer
Dozer依賴的第三方jar包

Eclipse外掛
Dozer有外掛可以在Eclipse中使用(不知道是否好用,反正我沒用過)
外掛地址:

http://dozer.sourceforge.net/eclipse-plugin
使用
將Dozer引入到工程中後,我們就可以來小試一番了。
實踐出真知,先以一個最簡單的例子來展示Dozer對映的處理過程。

準備
我們先準備兩個要互相對映的類
NotSameAttributeA.java

public class NotSameAttributeA {
    private long id;
    private String name;
    private Date date;
 
    // 省略getter/setter
}
NotSameAttributeB.java

public class NotSameAttributeB {
    private long id;
    private String value;
    private Date date;
 
    // 省略getter/setter
}


這兩個類存在屬性名不完全相同的情況:name 和 value。

Dozer的配置
為什麼要有對映配置?
如果要對映的兩個物件有完全相同的屬性名,那麼一切都很簡單。
只需要直接使用Dozer的API即可:

Mapper mapper = new DozerBeanMapper();
DestinationObject destObject =  
    mapper.map(sourceObject, DestinationObject.class);


但實際對映時,往往存在屬性名不同的情況。
所以,你需要一些配置來告訴Dozer應該轉換什麼,怎麼轉換。
注:官網著重建議:在現實應用中,最好不要每次對映物件時都建立一個Mapper例項來工作,這樣會產生不必要的開銷。如果你不使用IoC容器(如:spring)來管理你的專案,那麼,最好將Mapper定義為單例模式。

對映配置檔案
在src/test/resources目錄下新增dozer/dozer-mapping.xml檔案。
<mapping>標籤中允許你定義<class-a>和<class-b>,對應著相互對映的類。
<field>標籤裡定義要對映的特殊屬性。需要注意<a>和<class-a>對應,<b>和<class-b>對應,聰明的你,猜也猜出來了吧。

<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://dozer.sourceforge.net
          http://dozer.sourceforge.net/schema/beanmapping.xsd">
  <mapping date-format="yyyy-MM-dd">
    <class-a>org.zp.notes.spring.common.dozer.vo.NotSameAttributeA</class-a>
    <class-b>org.zp.notes.spring.common.dozer.vo.NotSameAttributeB</class-b>
    <field>
      <a>name</a>
      <b>value</b>
    </field>
  </mapping>
</mappings>


與Spring整合
配置DozerBeanMapperFactoryBean
在src/test/resources目錄下新增spring/spring-dozer.xml檔案。
Dozer與Spring的整合很便利,你只需要宣告一個DozerBeanMapperFactoryBean,
將所有的dozer對映配置檔案作為屬性注入到mappingFiles,DozerBeanMapperFactoryBean會載入這些規則。
spring-dozer.xml檔案範例

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
       default-autowire="byName" default-lazy-init="false">
 
  <bean id="mapper" class="org.dozer.spring.DozerBeanMapperFactoryBean">
    <property name="mappingFiles">
      <list>
        <value>classpath*:dozer/dozer-mapping.xml</value>
      </list>
    </property>
  </bean>
</beans>


自動裝配
至此,萬事具備,你只需要自動裝配mapper。

RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring/spring-dozer.xml"})
@TransactionConfiguration(defaultRollback = false)
public class DozerTest extends TestCase {
    @Autowired
    Mapper mapper;
 
    @Test
    public void testNotSameAttributeMapping() {
        NotSameAttributeA src = new NotSameAttributeA();
        src.setId(007);
        src.setName("邦德");
        src.setDate(new Date());
 
        NotSameAttributeB desc = mapper.map(src, NotSameAttributeB.class);
        Assert.assertNotNull(desc);
    }
}


執行一下單元測試,綠燈通過。

Dozer支援的資料型別轉換
Dozer可以自動做資料型別轉換。當前,Dozer支援以下資料型別轉換(都是雙向的)

Primitive to Primitive Wrapper
原型(int、long等)和原型包裝類(Integer、Long)

Primitive to Custom Wrapper
原型和定製的包裝

Primitive Wrapper to Primitive Wrapper
原型包裝類和包裝類

Primitive to Primitive
原型和原型

Complex Type to Complex Type

複雜型別和複雜型別

String to Primitive
字串和原型

String to Primitive Wrapper
字串和原型包裝類

String to Complex Type if the Complex Type contains a String constructor
字串和有字串構造器的複雜型別(類)

String to Map
字串和Map

Collection to Collection
集合和集合

Collection to Array
集合和陣列

Map to Complex Type
Map和複雜型別

Map to Custom Map Type
Map和定製Map型別

Enum to Enum
列舉和列舉

Each of these can be mapped to one another: java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.util.Calendar, java.util.GregorianCalendar
這些時間相關的常見類可以互換:java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.util.Calendar, java.util.GregorianCalendar

String to any of the supported Date/Calendar Objects.
字串和支援Date/Calendar的物件

Objects containing a toString() method that produces a long representing time in (ms) to any supported Date/Calendar object. 
如果一個物件的toString()方法返回的是一個代表long型的時間數值(單位:ms),就可以和任何支援Date/Calendar的物件轉換。

Dozer的對映配置
在前面的簡單例子中,我們體驗了一把Dozer的對映流程。但是兩個類進行對映,有很多複雜的情況,相應的,你也需要一些更復雜的配置。
Dozer有三種對映配置方式:

註解方式
API方式
XML方式
用註解來配置對映
Dozer 5.3.2版本開始支援註解方式配置對映(只有一個註解:@Mapping)。可以應對一些簡單的對映處理,複雜的就玩不轉了。
看一下@Mapping的宣告就可以知道,這個註解只能用於元素和方法。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface Mapping {
  String value() default "";
}


讓我們來試試吧:
TargetBean.java

public class SourceBean {
 
    private Long id;
 
    private String name;
 
    @Mapping("binaryData")
    private String data;
 
    @Mapping("pk")
    public Long getId() {
        return this.id;
    }
 
    //其餘getter/setter方法略
}
TargetBean.java

public class TargetBean {
 
    private String pk;
 
    private String name;
 
    private String binaryData;
 
    //getter/setter方法略
}


定義了兩個相互對映的Java類,只需要在源類中用@Mapping標記和目標類中對應的屬性就可以了。

@Test
public void testAnnotationMapping() {
    SourceBean src = new SourceBean();
    src.setId(7L);
    src.setName("邦德");
    src.setData("00000111");
 
    TargetBean desc = mapper.map(src, TargetBean.class);
    Assert.assertNotNull(desc);
}


測試一下,綠燈通過。
官方文件說,雖然當前版本(文件的版本對應Dozer 5.5.1)僅支援@Mapping,但是在未來的釋出版本會提供其他的註解功能,那就敬請期待吧(再次吐槽一下:一年多沒更新了)。

用API來配置對映
個人覺得這種方式比較麻煩,不推薦,也不想多做介紹,就是這麼任性。

用XML來配置對映
需要強調的是:如果兩個類的所有屬性都能很好的互轉,可以你中有我,我中有你,不分彼此,那麼就不要畫蛇添足的在xml中去宣告對映規則了。

屬性名不同時的對映(Basic Property Mapping)
Dozer會自動對映屬性名相同的屬性,所以不必新增在xml檔案中。

<field>
  <a>one</a>
  <b>onePrime</b>
</field>
字串和日期對映(String to Date Mapping)
字串在和日期進行對映時,允許使用者指定日期的格式。
格式的設定分為三個作用域級別:
屬性級別
對當前屬性有效(這個屬性必須是日期字串)

<field>
  <a date-format="MM/dd/yyyy HH:mm:ss:SS">dateString</a>
  <b>dateObject</b>
</field>
類級別
對這個類中的所有日期相關的屬性有效

<mapping date-format="MM-dd-yyyy HH:mm:ss">
  <class-a>org.dozer.vo.TestObject</class-a>
  <class-b>org.dozer.vo.TestObjectPrime</class-b>
  <field>
    <a>dateString</a>
    <b>dateObject</b>
  </field>
</mapping>


全域性級別
對整個檔案中的所有日期相關的屬性有效。

<mappings>
  <configuration>
    <date-format>MM/dd/yyyy HH:mm</date-format>
  </configuration>
 
  <mapping wildcard="true">
    <class-a>org.dozer.vo.TestObject</class-a>
    <class-b>org.dozer.vo.TestObjectPrime</class-b>
    <field>
      <a>dateString</a>
      <b>dateObject</b>
    </field>
  </mapping>
</mappings>


集合和陣列對映(Collection and Array Mapping)
Dozer可以自動處理以下型別的雙向轉換。

List to List
List to Array
Array to Array
Set to Set
Set to Array
Set to List
使用hint
如果使用泛型或陣列,沒有必要使用hint。
如果不使用泛型或陣列。在處理集合或陣列之間的轉換時,你需要用hint指定目標列表的資料型別。
若你不指定hint,Dozer將認為目標集合和源集合的型別是一致的。
使用Hints的範例:

<field>
  <a>hintList</a> 
  <b>hintList</b> 
  <b-hint>org.dozer.vo.TheFirstSubClassPrime</b-hint> 
</field> 


累計對映和非累計對映(Cumulative vs. Non-Cumulative List Mapping)
如果你要轉換的目標類已經初始化,你可以選擇讓Dozer新增或更新物件到你的集合中。
而這取決於relationship-type配置,預設是累計。
它的設定有作用域級別:
全域性級

<mappings>
  <configuration>
     <relationship-type>non-cumulative</relationship-type>
  </configuration>
</mappings>


類級別

<mappings>
  <mapping relationship-type="non-cumulative">
    <!-- 省略 -->  
  </mapping> 
</mappings>


屬性級別

<field relationship-type="cumulative">
  <a>hintList</a>
  <b>hintList</b> 
  <a-hint>org.dozer.vo.TheFirstSubClass</a-hint> 
  <b-hint>org.dozer.vo.TheFirstSubClassPrime</b-hint> 
</field>


移動孤兒(Removing Orphans)
這裡的孤兒是指目標集合中存在,但是源集合中不存在的元素。
你可以使用remove-orphans開關來選擇是否移除這樣的元素。

<field remove-orphans="true">
  <a>srcList</a> 
  <b>destList</b>  
</field>    


深度對映(Deep Mapping)
所謂深度對映,是指允許你指定屬性的屬性(比如一個類的屬性本身也是一個類)。
舉例來說
Source.java

public class Source {
    private long id;
    private String info;
}
Dest.java

public class Dest {
    private long id;
    private Info info;
}
public class Info {
    private String content;
}


對映規則

<mapping>
  <class-a>org.zp.notes.spring.common.dozer.vo.Source</class-a>
  <class-b>org.zp.notes.spring.common.dozer.vo.Dest</class-b>
  <field>
    <a>info</a>
    <b>info.content</b>
  </field>
</mapping>


排除屬性(Excluding Fields)
就像任何團體都有搗亂分子,類之間轉換時也有想要排除的因子。
如何在做型別轉換時,自動排除一些屬性,Dozer提供了幾種方法,這裡只介紹一種比較通用的方法。
更多詳情參考官網。
field-exclude可以排除不需要對映的屬性。

<field-exclude> 
  <a>fieldToExclude</a> 
  <b>fieldToExclude</b> 
</field-exclude>
單向對映(One-Way Mapping)
注:本文的對映方式,無特殊說明,都是雙向對映的。
有的場景可能希望轉換過程不可逆,即單向轉換。
單向轉換可以通過使用one-way來開啟
類級別

<mapping type="one-way"> 
  <class-a>org.dozer.vo.TestObjectFoo</class-a>
  <class-b>org.dozer.vo.TestObjectFooPrime</class-b>   
    <field>
      <a>oneFoo</a>
      <b>oneFooPrime</b>
    </field>
</mapping>  


屬性級別

<mapping> 
  <class-a>org.dozer.vo.TestObjectFoo2</class-a>
  <class-b>org.dozer.vo.TestObjectFooPrime2</class-b>   
  <field type="one-way">
    <a>oneFoo2</a>
    <b>oneFooPrime2</b>
  </field>
 
  <field type="one-way">
    <a>oneFoo3.prime</a>
    <b>oneFooPrime3</b>
  </field>


全域性配置(Global Configuration)
全域性配置用來設定全域性的配置資訊。此外,任何定製轉換都是在這裡定義的。
全域性配置都是可選的。
<date-format>表示日期格式
<stop-on-errors>錯誤處理開關
<wildcard>萬用字元
<trim-strings>裁剪字串開關

<configuration >
  
  <date-format>MM/dd/yyyy HH:mm</date-format>
  <stop-on-errors>true</stop-on-errors>
  <wildcard>true</wildcard>
  <trim-strings>false</trim-strings>
     
  <custom-converters> <!-- these are always bi-directional -->
    <converter type="org.dozer.converters.TestCustomConverter" >
      <class-a>org.dozer.vo.TestCustomConverterObject</class-a>
      <class-b>another.type.to.Associate</class-b>
    </converter>
     
  </custom-converters>     
</configuration>


全域性配置的作用是幫助你少配置一些引數,如果個別類的對映規則需要變更,你可以mapping中覆蓋它。
覆蓋的範例如下

<mapping date-format="MM-dd-yyyy HH:mm:ss"> 
  <!-- 省略 -->
</mapping>
 
<mapping wildcard="false">
  <!-- 省略 -->
</mapping> 
 
<mapping stop-on-errors="false"> 
  <!-- 省略 -->
</mapping>
 
<mapping trim-strings="true"> 
  <!-- 省略 -->
</mapping>      


定製轉換(Custom Converters)
如果Dozer預設的轉換規則不能滿足實際需要,你可以選擇定製轉換。
定製轉換通過配置XML來告訴Dozer如何去轉換兩個指定的類。當Dozer轉換這兩個指定類的時候,會呼叫你的對映規則去替換標準對映規則。
為了讓Dozer識別,你必須實現org.dozer.CustomConverter介面。否則,Dozer會拋異常。
具體做法:

(1) 建立一個類實現org.dozer.CustomConverter介面。

public class TestCustomConverter implements CustomConverter {
  
  public Object convert(Object destination, Object source, 
      Class destClass, Class sourceClass) {
    if (source == null) {
      return null;
    }
    CustomDoubleObject dest = null;
    if (source instanceof Double) {
      // check to see if the object already exists
      if (destination == null) {
        dest = new CustomDoubleObject();
      } else {
        dest = (CustomDoubleObject) destination;
      }
      dest.setTheDouble(((Double) source).doubleValue());
      return dest;
    } else if (source instanceof CustomDoubleObject) {
      double sourceObj = 
        ((CustomDoubleObject) source).getTheDouble();
      return new Double(sourceObj);
    } else {
      throw new MappingException("Converter TestCustomConverter "
          + "used incorrectly. Arguments passed in were:"
          + destination + " and " + source);
    }
  } 


(2) 在xml中引用定製的對映規則
引用定製的對映規則也是分級的,你可以酌情使用。
全域性級:

<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://dozer.sourceforge.net
          http://dozer.sourceforge.net/schema/beanmapping.xsd">
  <configuration>
    <!-- 總是雙向轉換的 -->
    <custom-converters>
      <converter type="org.dozer.converters.TestCustomConverter" >
        <class-a>org.dozer.vo.CustomDoubleObject</class-a>
        <class-b>java.lang.Double</class-b>
      </converter>
 
      <!-- You are responsible for mapping everything between 
           ClassA and ClassB -->
      <converter 
        type="org.dozer.converters.TestCustomHashMapConverter" >
        <class-a>org.dozer.vo.TestCustomConverterHashMapObject</class-a>
        <class-b>org.dozer.vo.TestCustomConverterHashMapPrimeObject</class-b>
      </converter>
    </custom-converters>     
  </configuration>
</mappings>


屬性級

<mapping>
  <class-a>org.dozer.vo.SimpleObj</class-a>
  <class-b>org.dozer.vo.SimpleObjPrime2</class-b>    
  <field custom-converter=
    "org.dozer.converters.TestCustomConverter">
    <a>field1</a>
    <b>field1Prime</b>
  </field>
</mapping>   


對映的繼承(Inheritance Mapping)
Dozer支援對映規則的繼承機制。
屬性如果有著相同的名字則不需要在xml中配置,除非使用了hint
我們來看一個例子

<mapping>
  <class-a>org.dozer.vo.SuperClass</class-a>
  <class-b>org.dozer.vo.SuperClassPrime</class-b>
    
  <field>
    <a>superAttribute</a>
    <b>superAttr</b>
  </field>
</mapping>  
 
<mapping>
  <class-a>org.dozer.vo.SubClass</class-a>
  <class-b>org.dozer.vo.SubClassPrime</class-b>
    
  <field>
    <a>attribute</a>
    <b>attributePrime</b>
  </field>
</mapping>
  
<mapping>
  <class-a>org.dozer.vo.SubClass2</class-a>
  <class-b>org.dozer.vo.SubClassPrime2</class-b>
     
  <field>
    <a>attribute2</a>
    <b>attributePrime2</b>
  </field>
</mapping>


在上面的例子中SubClass、SubClass2是SuperClass的子類;
SubClassPrime和SubClassPrime2是SuperClassPrime的子類。
superAttribute和superAttr的對映規則會被子類所繼承,所以不必再重複的在子類中去宣告。

參考
Dozer原始碼地址
Dozer官方文件

相關文章