前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实体拷贝工具大汇总,你还在用BeanUtils? 赶紧来学一学吧

实体拷贝工具大汇总,你还在用BeanUtils? 赶紧来学一学吧

作者头像
一缕82年的清风
发布2021-12-06 10:48:46
7670
发布2021-12-06 10:48:46
举报
文章被收录于专栏:lsqingfeng

我们在项目当中,经常会遇到实体拷贝的情况,必须把DO拷贝到BO, BO拷贝到VO等等,这个时候,如果我们还是单纯的使用get/set 会发现,代码可能会变得非常的臃肿,但不可置疑的是get/set不会有太大的坑。 所以实体拷贝工具有时候就成了程序的标配。今天就给大家介绍汇总一下常用的实体拷贝工具,并使用非常简单的例子来测试一下他们的性能,由于没有大量场景的测试,所以测试结果不代表最终结果,仅供大家参考。 还有就是我这里面汇总的工具,不像很多其他文章那样,都是一些比较老的工具,毕竟2020年了,这里囊括了dozer ,easyMapper, modelMapper等比较新的工具介绍给大家。

代码说明: 在下面的工具介绍中,我们会使用每种工具,拷贝一个简单的对象实体,并测试执行时间。所以我们先准备两个类结构一致的对象,然后给其中的一个对象赋值,将其拷贝给另一个对象。

源对象:

代码语言:javascript
复制
@Data
public class Person {
    private int id;

    private String name;

    private Boolean vipFlag;

    private BigDecimal price;

    private Date createTime;

    private LocalDateTime updateTime;
}

目标对象

代码语言:javascript
复制
@Data
public class PersonVO {
    private int id;

    private String name;

    private Boolean vipFlag;

    private BigDecimal price;

    private Date createTime;

    private LocalDateTime updateTime;
}

给Person对象赋值,然后通过下面的工具类拷贝到PersonVO中

代码语言:javascript
复制
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, 效率会比这个工具要高一些。

这个类的用法给大家截个图:

这时候可以看到这个方法的调用已经有红线了,原因就是我安装的阿里巴巴插件检测到了这个方法,直接不建议使用了。 另外他执行拷贝所消耗的时间确实很长

依赖:

代码语言:javascript
复制
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

二. 使用spring中的BeanUtils

代码语言:javascript
复制
说下这个BeanUtils,要注意的是,他的类名和上面说的那个类名是一模一样的,区别就是一个是apache的,一个是spring的, spring的这个只要你的项目中有spring就可以用,不需要单独导包。同时要注意他和上面那个类的用法十分相似,区别就是参数是相反的,另外这个类的执行效率也还不错,所以如果不想引入其他依赖的时候,可以使用这个类来替代上面的apache的方式。
代码语言:javascript
复制
        // 方式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毫秒,相当于瞬间完成。

代码语言:javascript
复制
         // 方式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的效率确实不高,感觉有点太重量级了,也有点老了,整体实力和第一个差不多。

依赖:

代码语言:javascript
复制
<!-- 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>

案例:

代码语言:javascript
复制
//方式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);
代码语言:javascript
复制

五. ModelMapper.

这也是一个实体拷贝类框架,需要引入依赖, 支持自定义映射, 支持List, Map拷贝,用法和dozer极为相似,在我的测试中效果表现很好,很快。比较推荐。

依赖:

代码语言:javascript
复制
<dependency>
       <groupId>org.modelmapper</groupId>
       <artifactId>modelmapper</artifactId>
       <version>2.3.0</version>
</dependency>

用法:

代码语言:javascript
复制
        // 方式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);

拷贝集合用法演示:

代码语言:javascript
复制
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快

详细用法:

代码语言:javascript
复制
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/

引入依赖:

代码语言:javascript
复制
<dependency>
       <groupId>com.baidu.unbiz</groupId>
        <artifactId>easy-mapper</artifactId>
        <version>1.0.4</version>
</dependency>

用法:

代码语言:javascript
复制
// 方式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: 表现也很一般。

引入依赖:

代码语言:javascript
复制
         <dependency>
            <groupId>ma.glasnost.orika</groupId>
            <artifactId>orika-core</artifactId>
            <version>1.5.2</version><!-- or latest version -->
        </dependency>

用法:

代码语言:javascript
复制
// 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.

本测试结果仅供参考, 未进行复杂多维度多条件多场景测试,不代表权威观点。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/06/12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档