前言
image.png如图所示,在开发之中,无论是MVC式的三层架构,还是DDD领域驱动式的架构。总会有各种DTO、DO、PO、VO之间的转换需求。所以我们经常会定义两层Object字段是保持一致的,便于防腐层Assember操作。但现实需求中也会遇到一些复杂映射。所以我们应该如何基于场景选择合适的BeanCopy框架呢?
这篇博客主要整理一下BeanCopy类框架。
- 各个框架性能表现
- 如何选择
BeanCopy框架
除了HardCopy之外(手写set get)
常用的BeanCopy选择有以下:
我直接给出一个performance报告
BeanCopy框架性能对比
结论图:
框架选择
我主要推两大类
- 基于MapStruct*Selma的注解式Mapper
MapStruct和Selma都是基于注解处理器实现的,关于注解处理器我单独写一篇blog介绍,到时候在这里新增链接。
MapStruct是基于JSR 269的Java注解处理器,在使用过程中需要只需要配置完成后运行 mvn compile就会发现 target文件夹中生成了一个mapper接口的实现类。打开实现类会发现实体类中自动生成了字段一一对应的get、set方法的文件。
比如我定义一个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);
}
编译之后你会发现target多了一个实现类
image.png
同样的道理我们看看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是非常相似的,原理一样,并且在注解和用法上几乎一样,我认为MapStruct更好的原因主要是社区更活跃,与SpringBoot集成更好,并且生成的代码更规范、简洁、漂亮。
- 基于Orika、JMapper的静态工具类(Dozer性能太差舍弃)
封装一下以下代码即可。
private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper = mapperFactory.getMapperFacade();
PersonDestination orikaDestination = mapper.map(source, PersonDestination.class);
如果是List互相转换
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);
实验
@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
实验结论:
1.不管使用实验中的哪种框架,在性能上其实绝对值相差不会太大(非第一次运行)。
2.个别框架拷贝后引用是PersonSourceSon,个别是PersonDestinationSon,说明不同框架在深浅拷贝方案上实现不同。
3.有字段名映射、ignore、格式化等需求时,不同框架支持的不同。
总结
1.在日常开发中,BeanCopy需求无非是三种
- 字段相同最简单的copy
- 有复杂性的copy(比如字段名称不同、有ignore需求、有格式化需求)
- 有业务逻辑的copy
那么针对以上三点,我认为
- 第一种以简单高效为主,我建议直接使用Orika工具类,实现非常简单,客户端编码非常少,基本上就是丢一个source和target type进去即可,保证了深拷贝,性能上高于Dozer等老产品,并且集合之间拷贝也很优秀。像BeanUtils、BeanCopier在很多场景表现明显不如Orika,会有各种问题备受吐槽。
- 第二种建议使用功能强大的MapStruct框架,它的好处呢,就是既生成了代码,比较直观方便debug。又支持非常多且强大的注解,可以轻松做到多层级之间字段映射、字段ignore、日期格式化、金额格式化等。还有mapping模版继承复用、组合等功能。还有就是天然支持Spring注入,SpringBoot集成等,在这一点上,相比较Dozer式的xml映射,注解是更符合现代编程方式的。
2.关于性能的取舍
我们通过性能测试可以发现,一旦运行过一次之后,上面几种框架单次copy性能绝对值都非常低(个别框架主要基于Asm开始的耗时、缓存原理、jvm热代码优化等原因第一次会久一点)。所以性能取舍上的考虑,主要基于量和系统场景。如果是特别夸张的并发,或者说真的系统到了需要优化类库提升性能的瓶颈上。这种低绝对值之间的相对差距才有意义,因为单次之间的差距是微秒级的,如果没有一个量的乘积放大,是可以忽略性能上的差异。正常大部分公司是没有这个需求的,没有必要追求这种极致的性能,所以考虑的更多是既处于一个"高性能"表现(绝对值),其它方面让你很满意的类库。
网友评论