1. 程式人生 > >Dozer掃盲級教程

Dozer掃盲級教程

前言

這篇文章是本人在閱讀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對映配置檔案作為屬性注入到mappingFilesDozerBeanMapperFactoryBean會載入這些規則。
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的對映規則會被子類所繼承,所以不必再重複的在子類中去宣告。

參考