如图所示,在开发之中,无论是MVC式的三层架构,还是DDD领域驱动式的架构。总会有各种DTO、DO、PO、VO之间的转换需求。所以我们经常会定义两层Object字段是保持一致的,便于防腐层Assember操作。但现实需求中也会遇到一些复杂映射。所以我们应该如何基于场景选择合适的BeanCopy框架呢?
这篇博客主要整理一下BeanCopy类框架。
除了HardCopy之外(手写set get) 常用的BeanCopy选择有以下:
我直接给出一个performance报告 BeanCopy框架性能对比 结论图:
我主要推两大类
@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多了一个实现类
同样的道理我们看看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集成更好,并且生成的代码更规范、简洁、漂亮。
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需求无非是三种
那么针对以上三点,我认为
2.关于性能的取舍
我们通过性能测试可以发现,一旦运行过一次之后,上面几种框架单次copy性能绝对值都非常低(个别框架主要基于Asm开始的耗时、缓存原理、jvm热代码优化等原因第一次会久一点)。所以性能取舍上的考虑,主要基于量和系统场景。如果是特别夸张的并发,或者说真的系统到了需要优化类库提升性能的瓶颈上。这种低绝对值之间的相对差距才有意义,因为单次之间的差距是微秒级的,如果没有一个量的乘积放大,是可以忽略性能上的差异。正常大部分公司是没有这个需求的,没有必要追求这种极致的性能,所以考虑的更多是既处于一个"高性能"表现(绝对值),其它方面让你很满意的类库。