前言
如圖所示,在開發(fā)之中腊徙,無論是MVC式的三層架構(gòu),還是DDD領(lǐng)域驅(qū)動式的架構(gòu)简十。總會有各種DTO撬腾、DO螟蝙、PO、VO之間的轉(zhuǎn)換需求时鸵。所以我們經(jīng)常會定義兩層Object字段是保持一致的胶逢,便于防腐層Assember操作。但現(xiàn)實(shí)需求中也會遇到一些復(fù)雜映射饰潜。所以我們應(yīng)該如何基于場景選擇合適的BeanCopy框架呢初坠?
這篇博客主要整理一下BeanCopy類框架。
- 各個(gè)框架性能表現(xiàn)
- 如何選擇
BeanCopy框架
除了HardCopy之外(手寫set get)
常用的BeanCopy選擇有以下:
我直接給出一個(gè)performance報(bào)告
BeanCopy框架性能對比
結(jié)論圖:
框架選擇
我主要推兩大類
- 基于MapStruct*Selma的注解式Mapper
MapStruct和Selma都是基于注解處理器實(shí)現(xiàn)的彭雾,關(guān)于注解處理器我單獨(dú)寫一篇blog介紹碟刺,到時(shí)候在這里新增鏈接。
MapStruct是基于JSR 269的Java注解處理器薯酝,在使用過程中需要只需要配置完成后運(yùn)行 mvn compile就會發(fā)現(xiàn) target文件夾中生成了一個(gè)mapper接口的實(shí)現(xiàn)類半沽。打開實(shí)現(xiàn)類會發(fā)現(xiàn)實(shí)體類中自動生成了字段一一對應(yīng)的get、set方法的文件吴菠。
比如我定義一個(gè)MapStruct接口(@Mapper注解支持IOC注入方式者填、我這里沒使用)
@Mapper
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
/**
* source -> destination
*
* @param car
* @return
*/
@Mappings({
@Mapping(source = "middleName", target = "middle"),
@Mapping(target = "email", ignore = true)
})
PersonDestination sourceToDestination(PersonSource car);
}
編譯之后你會發(fā)現(xiàn)target多了一個(gè)實(shí)現(xiàn)類
同樣的道理我們看看Selma
@Mapper
public interface SelmaPersonMapper {
SelmaPersonMapper INSTANCE = Selma.mapper(SelmaPersonMapper.class);
/**
* source -> destination
*
* @param car
* @return
* @Maps(withCustomFields = {
* @Field({"middleName", "middle"})
* }, withIgnoreFields = {"email"})
*/
@Maps(withCustomFields = {
@Field({"middleName", "middle"})
}, withIgnoreFields = {"email"})
PersonDestination sourceToDestination(PersonSource car);
}
所以Selma和MapStruct是非常相似的,原理一樣做葵,并且在注解和用法上幾乎一樣占哟,我認(rèn)為MapStruct更好的原因主要是社區(qū)更活躍,與SpringBoot集成更好酿矢,并且生成的代碼更規(guī)范榨乎、簡潔、漂亮瘫筐。
- 基于Orika蜜暑、JMapper的靜態(tài)工具類(Dozer性能太差舍棄)
封裝一下以下代碼即可。
private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper = mapperFactory.getMapperFacade();
PersonDestination orikaDestination = mapper.map(source, PersonDestination.class);
如果是List互相轉(zhuǎn)換
private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper = mapperFactory.getMapperFacade();
List<PersonSource> sourceList = Lists.newArrayList(source);
List<PersonDestination> personDestinations = mapper.mapAsList(sourceList, PersonDestination.class);
如果是字段名有映射的
private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(PersonSource.class, PersonDestination.class)
.field("firstName", "givenName")
.field("lastName", "sirName")
.byDefault()
.register();
MapperFacade mapper = mapperFactory.getMapperFacade();
PersonDestination destination = mapper.map(source, PersonDestination.class);
實(shí)驗(yàn)
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class PersonSourceComputer {
private String name;
private BigDecimal price;
}
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class PersonSourceSon {
private String sonName;
private List<PersonSourceComputer> computers;
}
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class PersonSource {
private String firstName;
private String middleName;
private String lastName;
private String email;
List<PersonSourceSon> son;
}
public class BeanCopyTest {
private static final Logger logger = LoggerFactory.getLogger(BeanCopyTest.class);
private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
//static {
// mapperFactory.classMap(PersonSource.class, PersonDestination.class).byDefault().register();
//}
public static void main(String[] args) {
for (int i = 1; i < 11; i++) {
beanCopyTest(i);
}
}
private static void beanCopyTest(int i) {
PersonSource source = initAndGetPersonSource();
Stopwatch stopwatch = Stopwatch.createStarted();
// MapStruct
PersonDestination destination = PersonMapper.INSTANCE.sourceToDestination(source);
System.out.println(destination);
stopwatch.stop();
logger.info("第" + i + "次" + "MapStruct cost:" + stopwatch.toString());
// Selma
stopwatch = Stopwatch.createStarted();
PersonDestination selmaDestination = SelmaPersonMapper.INSTANCE.sourceToDestination(source);
System.out.println(selmaDestination);
stopwatch.stop();
logger.info("第" + i + "次" + "Selma cost:" + stopwatch.toString());
// BeanUtils
stopwatch = Stopwatch.createStarted();
PersonDestination bUtilsDestination = new PersonDestination();
BeanUtils.copyProperties(source, bUtilsDestination);
System.out.println(bUtilsDestination);
stopwatch.stop();
logger.info("第" + i + "次" + "BeanUtils cost:" + stopwatch.toString());
// BeanCopier
stopwatch = Stopwatch.createStarted();
BeanCopier beanCopier = BeanCopier.create(PersonSource.class, PersonDestination.class, false);
PersonDestination bcDestination = new PersonDestination();
beanCopier.copy(source, bcDestination, null);
System.out.println(bcDestination);
stopwatch.stop();
logger.info("第" + i + "次" + "BeanCopier cost:" + stopwatch.toString());
// Orika
stopwatch = Stopwatch.createStarted();
MapperFacade mapper = mapperFactory.getMapperFacade();
PersonDestination orikaDestination = mapper.map(source, PersonDestination.class);
System.out.println(orikaDestination);
stopwatch.stop();
logger.info("第" + i + "次" + "Orika cost:" + stopwatch.toString());
}
private static PersonSource initAndGetPersonSource() {
PersonSource source = new PersonSource();
// set some field values
source.setFirstName("firstName");
source.setMiddleName("middleName");
source.setLastName("lastName");
source.setEmail("email");
source.setSon(Lists.newArrayList(new PersonSourceSon(
"sonName", Lists.newArrayList(new PersonSourceComputer("macBook", BigDecimal.valueOf(15000)))
)));
return source;
}
}
17:56:30.029 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次MapStruct cost:4.179 ms
17:56:30.035 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次Selma cost:2.727 ms
17:56:30.095 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次BeanUtils cost:59.65 ms
17:56:30.139 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次BeanCopier cost:43.52 ms
17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次Orika cost:167.1 ms
17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次MapStruct cost:88.97 μs
17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次Selma cost:36.72 μs
17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次BeanUtils cost:68.76 μs
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次BeanCopier cost:62.75 μs
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次Orika cost:108.0 μs
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次MapStruct cost:63.29 μs
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次Selma cost:71.12 μs
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次BeanUtils cost:81.64 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次BeanCopier cost:68.01 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次Orika cost:112.4 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次MapStruct cost:54.27 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次Selma cost:37.97 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次BeanUtils cost:124.3 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次BeanCopier cost:124.9 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次Orika cost:107.3 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次MapStruct cost:43.39 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次Selma cost:50.03 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次BeanUtils cost:75.00 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次BeanCopier cost:50.83 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次Orika cost:92.18 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次MapStruct cost:34.60 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次Selma cost:61.26 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次BeanUtils cost:118.6 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次BeanCopier cost:102.7 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次Orika cost:170.5 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次MapStruct cost:65.29 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次Selma cost:52.06 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次BeanUtils cost:86.51 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次BeanCopier cost:101.3 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次Orika cost:119.0 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次MapStruct cost:56.88 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次Selma cost:35.56 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次BeanUtils cost:98.93 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次BeanCopier cost:69.25 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次Orika cost:95.27 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次MapStruct cost:33.60 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次Selma cost:31.90 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次BeanUtils cost:96.19 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次BeanCopier cost:77.15 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次Orika cost:95.17 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次MapStruct cost:45.55 μs
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Selma cost:32.28 μs
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次BeanUtils cost:62.06 μs
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次BeanCopier cost:46.78 μs
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Orika cost:113.6 μs
17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次Orika cost:167.1 ms
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次Orika cost:108.0 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次Orika cost:112.4 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次Orika cost:107.3 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次Orika cost:92.18 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次Orika cost:170.5 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次Orika cost:119.0 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次Orika cost:95.27 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次Orika cost:95.17 μs
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Orika cost:113.6 μs
17:56:30.029 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次MapStruct cost:4.179 ms
17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次MapStruct cost:88.97 μs
17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次MapStruct cost:63.29 μs
17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次MapStruct cost:54.27 μs
17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次MapStruct cost:43.39 μs
17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次MapStruct cost:34.60 μs
17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次MapStruct cost:65.29 μs
17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次MapStruct cost:56.88 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次MapStruct cost:33.60 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次MapStruct cost:45.55 μs
17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次MapStruct cost:45.55 μs
PersonDestination(firstName=firstName, middle=middleName, lastName=lastName, email=null, son=[PersonDestinationSon(sonName=sonName, computers=[PersonDestinationComputer(name=macBook, price=15000)])])
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Selma cost:32.28 μs
PersonDestination(firstName=firstName, middle=null, lastName=lastName, email=email, son=[PersonSourceSon(sonName=sonName, computers=[PersonSourceComputer(name=macBook, price=15000)])])
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次BeanUtils cost:62.06 μs
PersonDestination(firstName=firstName, middle=null, lastName=lastName, email=email, son=[PersonSourceSon(sonName=sonName, computers=[PersonSourceComputer(name=macBook, price=15000)])])
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次BeanCopier cost:46.78 μs
PersonDestination(firstName=firstName, middle=null, lastName=lastName, email=email, son=[PersonDestinationSon(sonName=sonName, computers=[PersonDestinationComputer(name=macBook, price=15000)])])
17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Orika cost:113.6 μs
實(shí)驗(yàn)結(jié)論:
1.不管使用實(shí)驗(yàn)中的哪種框架策肝,在性能上其實(shí)絕對值相差不會太大(非第一次運(yùn)行)肛捍。
2.個(gè)別框架拷貝后引用是PersonSourceSon隐绵,個(gè)別是PersonDestinationSon,說明不同框架在深淺拷貝方案上實(shí)現(xiàn)不同拙毫。
3.有字段名映射氢橙、ignore、格式化等需求時(shí)恬偷,不同框架支持的不同悍手。
總結(jié)
1.在日常開發(fā)中,BeanCopy需求無非是三種
- 字段相同最簡單的copy
- 有復(fù)雜性的copy(比如字段名稱不同、有ignore需求袍患、有格式化需求)
- 有業(yè)務(wù)邏輯的copy
那么針對以上三點(diǎn)坦康,我認(rèn)為
- 第一種以簡單高效為主,我建議直接使用Orika工具類诡延,實(shí)現(xiàn)非常簡單滞欠,客戶端編碼非常少,基本上就是丟一個(gè)source和target type進(jìn)去即可肆良,保證了深拷貝筛璧,性能上高于Dozer等老產(chǎn)品,并且集合之間拷貝也很優(yōu)秀惹恃。像BeanUtils夭谤、BeanCopier在很多場景表現(xiàn)明顯不如Orika,會有各種問題備受吐槽巫糙。
- 第二種建議使用功能強(qiáng)大的MapStruct框架朗儒,它的好處呢,就是既生成了代碼参淹,比較直觀方便debug醉锄。又支持非常多且強(qiáng)大的注解,可以輕松做到多層級之間字段映射浙值、字段ignore恳不、日期格式化、金額格式化等开呐。還有mapping模版繼承復(fù)用烟勋、組合等功能。還有就是天然支持Spring注入负蚊,SpringBoot集成等神妹,在這一點(diǎn)上颓哮,相比較Dozer式的xml映射家妆,注解是更符合現(xiàn)代編程方式的。
2.關(guān)于性能的取舍
我們通過性能測試可以發(fā)現(xiàn)冕茅,一旦運(yùn)行過一次之后伤极,上面幾種框架單次copy性能絕對值都非常低(個(gè)別框架主要基于Asm開始的耗時(shí)蛹找、緩存原理、jvm熱代碼優(yōu)化等原因第一次會久一點(diǎn))哨坪。所以性能取舍上的考慮庸疾,主要基于量和系統(tǒng)場景。如果是特別夸張的并發(fā)当编,或者說真的系統(tǒng)到了需要優(yōu)化類庫提升性能的瓶頸上届慈。這種低絕對值之間的相對差距才有意義,因?yàn)閱未沃g的差距是微秒級的忿偷,如果沒有一個(gè)量的乘積放大金顿,是可以忽略性能上的差異。正常大部分公司是沒有這個(gè)需求的鲤桥,沒有必要追求這種極致的性能揍拆,所以考慮的更多是既處于一個(gè)"高性能"表現(xiàn)(絕對值),其它方面讓你很滿意的類庫茶凳。