BeanCopy框架终极指南

📁 36563688 📅 2025-08-10 21:05:32 👤 admin 👁️ 5076 ❤️ 751
BeanCopy框架终极指南

前言

image.png

如图所示,在开发之中,无论是MVC式的三层架构,还是DDD领域驱动式的架构。总会有各种DTO、DO、PO、VO之间的转换需求。所以我们经常会定义两层Object字段是保持一致的,便于防腐层Assember操作。但现实需求中也会遇到一些复杂映射。所以我们应该如何基于场景选择合适的BeanCopy框架呢?

这篇博客主要整理一下BeanCopy类框架。

各个框架性能表现

如何选择

BeanCopy框架

除了HardCopy之外(手写set get)

常用的BeanCopy选择有以下:

Dozer

BeanCopier

BeanUtils

MapStruct

ModelMapper

Selma

Orika

JMapper

我直接给出一个performance报告

BeanCopy框架性能对比

结论图:

image.png

框架选择

我主要推两大类

基于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 sourceList = Lists.newArrayList(source);

List 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 computers;

}

@NoArgsConstructor

@AllArgsConstructor

@Setter

@Getter

@ToString

public class PersonSource {

private String firstName;

private String middleName;

private String lastName;

private String email;

List 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热代码优化等原因第一次会久一点)。所以性能取舍上的考虑,主要基于量和系统场景。如果是特别夸张的并发,或者说真的系统到了需要优化类库提升性能的瓶颈上。这种低绝对值之间的相对差距才有意义,因为单次之间的差距是微秒级的,如果没有一个量的乘积放大,是可以忽略性能上的差异。正常大部分公司是没有这个需求的,没有必要追求这种极致的性能,所以考虑的更多是既处于一个"高性能"表现(绝对值),其它方面让你很满意的类库。

相关推荐