常見Bean映射轉(zhuǎn)換工具分析評測及Orika介紹

原地址:http://tech.dianwoda.com/2017/11/04/gao-xing-neng-te-xing-feng-fu-de-beanying-she-gong-ju-orika/?utm_source=tuicool&utm_medium=referral

Bean映射工具選擇

工作中,我們經(jīng)常需要將對象轉(zhuǎn)換成不同的形式以適應(yīng)不同的api,或者在不同業(yè)務(wù)層中傳輸對象而不同分層的對象存在不同的格式,因此我們需要編寫映射代碼將對象中的屬性值從一種類型轉(zhuǎn)換成另一種類型。

進行這種轉(zhuǎn)換除了手動編寫大量的get/set代碼,還可以使用一些方便的類庫,常用的有apache的BeanUtils,spring的BeanUtils,cglib的BeanCopier。

BeanUtils

apache的BeanUtils和spring的BeanUtils中拷貝方法的原理都是先用jdk中 java.beans.Introspector類的getBeanInfo()方法獲取對象的屬性信息及屬性get/set方法,接著使用反射(Methodinvoke(Object obj, Object... args))方法進行賦值。apache支持名稱相同但類型不同的屬性的轉(zhuǎn)換,spring支持忽略某些屬性不進行映射,他們都設(shè)置了緩存保存已解析過的BeanInfo信息。

BeanCopier

cglib的BeanCopier采用了不同的方法:它不是利用反射對屬性進行賦值,而是直接使用ASM的MethodVisitor直接編寫各屬性的get/set方法(具體過程可見BeanCopier類的generateClass(ClassVisitor v)方法)生成class文件,然后進行執(zhí)行。由于是直接生成字節(jié)碼執(zhí)行,所以BeanCopier的性能較采用反射的BeanUtils有較大提高,這一點可在后面的測試中看出。

Dozer

使用以上類庫雖然可以不用手動編寫get/set方法,但是他們都不能對不同名稱的對象屬性進行映射。在定制化的屬性映射方面做得比較好的有Dozer,Dozer支持簡單屬性映射、復(fù)雜類型映射、雙向映射、隱式映射以及遞歸映射。可使用xml或者注解進行映射的配置,支持自動類型轉(zhuǎn)換,使用方便。但Dozer底層是使用reflect包下Field類的set(Object obj, Object value)方法進行屬性賦值,執(zhí)行速度上不是那么理想。

Orika

那么有沒有特性豐富,速度又快的Bean映射工具呢,這就是下面要介紹的Orika,Orika是近期在github活躍的項目,底層采用了javassist類庫生成Bean映射的字節(jié)碼,之后直接加載執(zhí)行生成的字節(jié)碼文件,因此在速度上比使用反射進行賦值會快很多,下面詳細介紹Orika的使用方法。

Orika使用

依賴

<dependency>  
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.2</version><!-- or latest version -->
</dependency>  

簡單映射

  1. 構(gòu)造一個MapperFactory
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();  

  1. 注冊字段映射
mapperFactory.classMap(PersonSource.class, PersonDestination.class)  
   .field("firstName", "givenName")
   .field("lastName", "sirName")
   .byDefault()
   .register();

  1. 進行映射
MapperFacade mapper = mapperFactory.getMapperFacade();

PersonSource source = new PersonSource();  
// set some field values
...
// map the fields of 'source' onto a new instance of PersonDest
PersonDest destination = mapper.map(source, PersonDest.class);  

在第二步進行的字段映射是雙向的,我們可以從目標類型映射回源類型,byDefault()方法用于注冊名稱相同的屬性(如果所有屬性名稱都相同則可以省略第2步),如果不希望某個字段參與映射,可以使用exclude方法

復(fù)雜映射

數(shù)組和List的映射

如果在目標類和目的類中分別有下面的屬性

class BasicPerson {  
  private List<String> nameParts;
  // getters/setters omitted
}
class BasicPersonDto {  
  private String firstName;
  private String lastName;
  // getters/setters omitted
}

可以使用下面的方式進行映射:

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)  
   .field("nameParts[0]", "firstName")
   .field("nameParts[1]", "lastName")
   .register();

類類型的映射

class Name {  
   private String first;
   private String last;
   private String fullName;
   // getters/setters 
}

class BasicPerson {  
  private Name name;
  // getters/setters omitted
}
class BasicPersonDto {  
  private String firstName;
  // getters/setters omitted
}

使用:

mapperFactory.classMap(BasicPerson.class, BasicPersonDto.class)  
   .field("name.first", "firstName")
   .register();

自定義轉(zhuǎn)換器

orika同樣支持自定義轉(zhuǎn)換器,將指定類型或指定名稱的屬性做映射時添加自定義操作,例如,將String類型的或某個屬性映射后加一個前綴,或者將Integer類型映射后加1等:

public class MyConverter extends CustomConverter<Date,MyDate> {  
   public MyDate convert(Date source, Type<? extends MyDate> destinationType) {
      // return a new instance of destinationType with all properties filled 
      //example:source + 1;
   }
}

Date為源類型中要做轉(zhuǎn)換的屬性數(shù)據(jù)類型,例如String、Integer等,MyDate為目標類型中要做轉(zhuǎn)換的屬性數(shù)據(jù)類型。

如果需要定義全局范圍的轉(zhuǎn)換:

ConverterFactory converterFactory = mapperFactory.getConverterFactory();  
converterFactory.registerConverter(new MyConverter());  

如果僅需要某幾個屬性使用轉(zhuǎn)換器:

ConverterFactory converterFactory = mapperFactory.getConverterFactory();  
converterFactory.registerConverter("myConverterIdValue", new MyConverter());

mapperFactory.classMap( Source.class, Destination.class )  
   .fieldMap("sourceField1", "sourceField2").converter("myConverterIdValue").add()
   ...
   .register();

其他說明

  1. Orika支持遞歸映射,將映射嵌套類直到用“簡單”類型完成映射。它還包含故障保險,以正確處理正在嘗試映射的對象中的遞歸引用。

  2. 在于spring集成時,可以將MapperFactory設(shè)置為單例

各映射工具的性能測試

構(gòu)造一個包含普通類型及類類型的Bean對象,使用jmh微基準框架進行測試。由于jvm會對熱點代碼進行優(yōu)化:方法反射調(diào)用次數(shù)超過閾值時會生成一個專用的MethodAccessor實現(xiàn)類,生成其中的invoke()方法的字節(jié)碼進行執(zhí)行。

故測試時每種方法先預(yù)熱執(zhí)行15次,而后再執(zhí)行100次獲取每次執(zhí)行的平均時間:

Benchmark                     Mode  Samples   Score  Score error  Units  
o.s.MyBenchmark.apache        avgt      100  25.246        0.535  us/op  
o.s.MyBenchmark.beanCopier    avgt      100   0.004        0.000  us/op  
o.s.MyBenchmark.byHand        avgt      100   0.004        0.000  us/op  
o.s.MyBenchmark.dozer         avgt      100   5.855        0.260  us/op  
o.s.MyBenchmark.orika         avgt      100   0.353        0.017  us/op  
o.s.MyBenchmark.spring        avgt      100   0.627        0.020  us/op  

統(tǒng)計報告中Units單位為微秒/次,由Score項可以看出,基于ASM的cglib BeanCopier拷貝速度基本和手寫get/set方法的速度無異,其次的就是基于javassist的Orika了,Orika的速度是spring BeanUtils的兩倍,Dozer的20倍,Apache BeanUtils的120倍。

綜上,當(dāng)屬性名和屬性類型完全相同時使用BeanCopier是最好的選擇,當(dāng)存在屬性名稱不同或者屬性名稱相同但屬性類型不同的情況時,使用Orika是一種不錯的選擇。如果你對Orika感到不放心,實際應(yīng)用前可以寫個測試類查看它的轉(zhuǎn)換結(jié)果是否符合預(yù)期。

作者:小玲子之凌空蹈虛
鏈接:http://www.itdecent.cn/p/40e0e64797b9
來源:簡書
簡書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容