我们在项目当中,经常会遇到实体拷贝的情况,必须把DO拷贝到BO, BO拷贝到VO等等,这个时候,如果我们还是单纯的使用get/set 会发现,代码可能会变得非常的臃肿,但不可置疑的是get/set不会有太大的坑。 所以实体拷贝工具有时候就成了程序的标配。今天就给大家介绍汇总一下常用的实体拷贝工具,并使用非常简单的例子来测试一下他们的性能,由于没有大量场景的测试,所以测试结果不代表最终结果,仅供大家参考。 还有就是我这里面汇总的工具,不像很多其他文章那样,都是一些比较老的工具,毕竟2020年了,这里囊括了dozer ,easyMapper, modelMapper等比较新的工具介绍给大家。
代码说明: 在下面的工具介绍中,我们会使用每种工具,拷贝一个简单的对象实体,并测试执行时间。所以我们先准备两个类结构一致的对象,然后给其中的一个对象赋值,将其拷贝给另一个对象。
源对象:
@Data
public class Person {
private int id;
private String name;
private Boolean vipFlag;
private BigDecimal price;
private Date createTime;
private LocalDateTime updateTime;
}
目标对象
@Data
public class PersonVO {
private int id;
private String name;
private Boolean vipFlag;
private BigDecimal price;
private Date createTime;
private LocalDateTime updateTime;
}
给Person对象赋值,然后通过下面的工具类拷贝到PersonVO中
public class BeanCopyDemo {
@Test
public void test() throws Exception{
// 先设置一个有值的对象
Person p = new Person();
p.setId(1);
p.setName("张三");
p.setPrice(new BigDecimal("100"));
p.setVipFlag(true);
p.setCreateTime(new Date());
p.setUpdateTime(LocalDateTime.now());
System.out.println(p);
// 各工具的拷贝方法......
}
}
一. apache BeanUtils:
这款工具不用多介绍了,相信很多人最开始做拷贝的时候都会用过,我这里不详细讲了,也极度不推荐大家使用,因为在阿里巴巴的编程规范中也已经不推荐使用这个方法了,原因就是效率太差,所以也不推荐大家使用,如果大家非要用apache的话,可以使用PropertyUtils, 效率会比这个工具要高一些。
这个类的用法给大家截个图:
这时候可以看到这个方法的调用已经有红线了,原因就是我安装的阿里巴巴插件检测到了这个方法,直接不建议使用了。 另外他执行拷贝所消耗的时间确实很长
依赖:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
二. 使用spring中的BeanUtils
说下这个BeanUtils,要注意的是,他的类名和上面说的那个类名是一模一样的,区别就是一个是apache的,一个是spring的, spring的这个只要你的项目中有spring就可以用,不需要单独导包。同时要注意他和上面那个类的用法十分相似,区别就是参数是相反的,另外这个类的执行效率也还不错,所以如果不想引入其他依赖的时候,可以使用这个类来替代上面的apache的方式。
// 方式2: 使用springBeanUtils
start = System.currentTimeMillis();
p2 = new PersonVO();
org.springframework.beans.BeanUtils.copyProperties(p, p2);
// spring BeanUtils 耗时:56毫秒
System.out.println("spring BeanUtils 耗时:" + (System.currentTimeMillis() - start) + "毫秒");
System.out.println("拷贝之后结果:" + p2);
System.out.println("----------------------------");
这段代码和上面的是接上的,由于两个类名相同,这里只能使用全类名进行调用,尤其zhuyi9参数顺序,有值的在前面,目标在后面。和上面的方法区分开。
三. cglib BeanCopier
接下来介绍的是cglib中的BeanCopier类,这个是cglib的, cglib我想大家应该知道吧,不知道的是不是也有点耳熟,一般面试的时候经常会被问道,spring中的aop的实现原理,一种是使用java中的动态代理,还有是cglib,cglib是通过底层字节码的方式实现的。同理他里边的BeanCopier在拷贝类的时候也是通过字节码的方式实现的,所以效率很高。不夸张的说,这个类应该是众多实体拷贝的方式中综合成绩最高的,我参考的很多其他文章也都是这个类的效率第一。 所以如果对于效率要求比较高的情况下,建议选择这个类,同时要注意,这个类在使用的时候有一个初始化的过程,我们可以把初始化的对象缓存起来,网上有比较多的案例,大家可以参考,去掉初始化的时间,我用这个类拷贝的结果是 0毫秒,相当于瞬间完成。
// 方式3: 使用BeanCopier 是cglib提供的
p2 = new PersonVO();
start = System.currentTimeMillis();
// cglib BeanCopier 不算初始化耗时:0毫秒 算上初始化40毫秒
BeanCopier beanCopier = BeanCopier.create(Person.class, PersonVO.class, false);
// p 是源 p2 是目标
beanCopier.copy(p, p2, null);
System.out.println("cglib BeanCopier 耗时:" + (System.currentTimeMillis() - start) + "毫秒");
System.out.println("拷贝之后结果:" + p2);
System.out.println("----------------------------");
四. Dozer
dozer: 这是一个实体拷贝的框架,相当于是专门干这件事的,我相信应该有不少朋友用过,因为我们在实体拷贝的过程中一直存在一个痛点就是深拷贝。上面几种工具都是做的浅拷贝,相当于你的类中如果还嵌套了其他对象是拷贝不了的。而dozer是支持深拷贝的,并且支持不同字段名名字的映射。比如你想把address 拷贝到 addr 上这种场景也是支持的。同时dozer有一个问题,在这必须提一下,就是dozer本身不支持jdk8 中的LocalDateTime的,使用这个类型会报错。如果非要使用,我们可以在依赖一个dozer支持jdk8的插件,所以比较麻烦,另外dozer的效率确实不高,感觉有点太重量级了,也有点老了,整体实力和第一个差不多。
依赖:
<!-- dozer -->
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.4.0</version>
</dependency>
<dependency>
<groupId>io.craftsman</groupId>
<artifactId>dozer-jdk8-support</artifactId>
<version>1.0.6</version>
</dependency>
案例:
//方式4: dozer: 一个实体拷贝框架,支持深拷贝,可自定义映射(名称不同的字段间进行拷贝)
// 需引入jar包,需兼容jdk8
// 并且不支持localDateTime
DozerBeanMapper mapper = new DozerBeanMapper();
mapper.setMappingFiles(Collections.singletonList("dozerJdk8Converters.xml"));
start = System.currentTimeMillis();
PersonVO p3 = mapper.map(p, PersonVO.class);
// dozer 耗时:119毫秒
System.out.println("dozer 耗时:" + (System.currentTimeMillis() - start) + "毫秒");
System.out.println("拷贝之后结果:" + p3);
五. ModelMapper.
这也是一个实体拷贝类框架,需要引入依赖, 支持自定义映射, 支持List, Map拷贝,用法和dozer极为相似,在我的测试中效果表现很好,很快。比较推荐。
依赖:
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.0</version>
</dependency>
用法:
// 方式5: ModelMap
// 需引入jar: org.modelmapper
// 支持自定义映射,支持List map
start = System.currentTimeMillis();
ModelMapper modelMapper = new ModelMapper();
PersonVO p4 = modelMapper.map(p, PersonVO.class);
// modelMapper 耗时:37毫秒
System.out.println("modelMapper 耗时:" + (System.currentTimeMillis() - start) + "毫秒");
System.out.println("拷贝之后结果:" + p4);
拷贝集合用法演示:
List<EventDO> eventList = eventService.list(new LambdaQueryWrapper<EventDO>().in(EventDO::getEventCode, eventCodeArr));
ModelMapper modelMapper = new ModelMapper();
List<EvenVO>list = modelMapper.map(eventList, new TypeToken<List<EventVO>>() {}.getType());
六. EasyMapper用法:
easyMapper也是干这个活的,百度出品的,但是说实话效率没有ModeMapper快,比dozer快
详细用法:
http://neoremind.com/2016/08/easy-mapper-%E4%B8%80%E4%B8%AA%E7%81%B5%E6%B4%BB%E5%8F%AF%E6%89%A9%E5%B1%95%E7%9A%84%E9%AB%98%E6%80%A7%E8%83%BDbean-mapping%E7%B1%BB%E5%BA%93/
引入依赖:
<dependency>
<groupId>com.baidu.unbiz</groupId>
<artifactId>easy-mapper</artifactId>
<version>1.0.4</version>
</dependency>
用法:
// 方式6: EasyMapper:
// http://neoremind.com/2016/08/easy-mapper-%E4%B8%80%E4%B8%AA%E7%81%B5%E6%B4%BB%E5%8F%AF%E6%89%A9%E5%B1%95%E7%9A%84%E9%AB%98%E6%80%A7%E8%83%BDbean-mapping%E7%B1%BB%E5%BA%93/
start = System.currentTimeMillis();
PersonVO p5 = MapperFactory.getCopyByRefMapper()
.mapClass(Person.class, PersonVO.class)
.registerAndMap(p, PersonVO.class);
// easyMapper 耗时:88毫秒
System.out.println("easyMapper 耗时:" + (System.currentTimeMillis() - start) + "毫秒");
System.out.println("拷贝之后结果:" + p5);
七. orika: 表现也很一般。
引入依赖:
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.2</version><!-- or latest version -->
</dependency>
用法:
// 6. orika:125ms
start = System.currentTimeMillis();
DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade orikaMapper = mapperFactory.getMapperFacade();
PersonVO p6 = orikaMapper.map(p, PersonVO.class);
System.out.println("orika 耗时:" + (System.currentTimeMillis() - start) + "毫秒");
System.out.println("拷贝之后结果:" + p6);
好了,目前比较主流的拷贝方法就大家介绍到这里。
如果追求速度,果断选择 cdlib, 并将初始化过程缓存起来,方便复用
如果追求实用方便,能深拷贝,自定义拷贝,拷贝集合,推荐选择ModelMapper.
本测试结果仅供参考, 未进行复杂多维度多条件多场景测试,不代表权威观点。