常見Bean映射轉(zhuǎn)換工具分析評(píng)測(cè)及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)常需要將對(duì)象轉(zhuǎn)換成不同的形式以適應(yīng)不同的api首启,或者在不同業(yè)務(wù)層中傳輸對(duì)象而不同分層的對(duì)象存在不同的格式钟沛,因此我們需要編寫映射代碼將對(duì)象中的屬性值從一種類型轉(zhuǎn)換成另一種類型。

進(jìn)行這種轉(zhuǎn)換除了手動(dòng)編寫大量的get/set代碼震庭,還可以使用一些方便的類庫魏身,常用的有apache的BeanUtils刀脏,spring的BeanUtils,cglib的BeanCopier最住。

BeanUtils

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

BeanCopier

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

Dozer

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

Orika

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

Orika使用

依賴

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

簡單映射

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

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

  1. 進(jìn)行映射
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);  

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

復(fù)雜映射

數(shù)組和List的映射

如果在目標(biāo)類和目的類中分別有下面的屬性

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

可以使用下面的方式進(jìn)行映射:

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)換器纱烘,將指定類型或指定名稱的屬性做映射時(shí)添加自定義操作杨拐,例如,將String類型的或某個(gè)屬性映射后加一個(gè)前綴擂啥,或者將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為目標(biāo)類型中要做轉(zhuǎn)換的屬性數(shù)據(jù)類型。

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

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

如果僅需要某幾個(gè)屬性使用轉(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支持遞歸映射山宾,將映射嵌套類直到用“簡單”類型完成映射至扰。它還包含故障保險(xiǎn),以正確處理正在嘗試映射的對(duì)象中的遞歸引用塌碌。

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

各映射工具的性能測(cè)試

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

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

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)計(jì)報(bào)告中Units單位為微秒/次,由Score項(xiàng)可以看出切厘,基于ASM的cglib BeanCopier拷貝速度基本和手寫get/set方法的速度無異萨咳,其次的就是基于javassist的Orika了,Orika的速度是spring BeanUtils的兩倍疫稿,Dozer的20倍培他,Apache BeanUtils的120倍。

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宽堆,隨后出現(xiàn)的幾起案子腌紧,更是在濱河造成了極大的恐慌,老刑警劉巖畜隶,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壁肋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡籽慢,警方通過查閱死者的電腦和手機(jī)墩划,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嗡综,“玉大人,你說我怎么就攤上這事杜漠〖埃” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵驾茴,是天一觀的道長盼樟。 經(jīng)常有香客問我,道長锈至,這世上最難降的妖魔是什么晨缴? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮峡捡,結(jié)果婚禮上击碗,老公的妹妹穿的比我還像新娘筑悴。我一直安慰自己,他們只是感情好稍途,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布阁吝。 她就那樣靜靜地躺著,像睡著了一般械拍。 火紅的嫁衣襯著肌膚如雪突勇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天坷虑,我揣著相機(jī)與錄音甲馋,去河邊找鬼。 笑死迄损,一個(gè)胖子當(dāng)著我的面吹牛定躏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播海蔽,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼共屈,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了党窜?” 一聲冷哼從身側(cè)響起拗引,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎幌衣,沒想到半個(gè)月后打月,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祟霍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年褒翰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楚里。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡断部,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出班缎,到底是詐尸還是另有隱情蝴光,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布达址,位于F島的核電站蔑祟,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏沉唠。R本人自食惡果不足惜疆虚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧径簿,春花似錦罢屈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至暗赶,卻和暖如春鄙币,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹂随。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國打工十嘿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岳锁。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓绩衷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親激率。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咳燕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容