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方法钞澳,接著使用反射(Method
的invoke(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>
簡單映射
- 構(gòu)造一個(gè)MapperFactory
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
- 注冊(cè)字段映射
mapperFactory.classMap(PersonSource.class, PersonDestination.class)
.field("firstName", "givenName")
.field("lastName", "sirName")
.byDefault()
.register();
- 進(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();
其他說明
Orika支持遞歸映射山宾,將映射嵌套類直到用“簡單”類型完成映射至扰。它還包含故障保險(xiǎn),以正確處理正在嘗試映射的對(duì)象中的遞歸引用塌碌。
在于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)并注明出處懊烤。