實體拷貝工具大彙總,你還在用BeanUtils? 趕緊來學一學吧

我們在項目當中,經常會遇到實體拷貝的情況,必須把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. 

本測試結果僅供參考, 未進行復雜多維度多條件多場景測試,不代表權威觀點。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章