Java对象属性拷贝工具对比分析

  • 1. 对象属性拷贝概述
  • 2. 对象属性拷贝工具
  • 2.1 拷贝工具对比
  • 2.2 拷贝工具验证
  • 3. 对象属性拷贝实现
  • 3.1 集合对象拷贝
  • 3.2 对象拷贝验证


1. 对象属性拷贝概述

在开发中经常遇到对象属性拷贝功能,而对象属性拷贝方式很多,比如手动set赋值,虽然麻烦,但是性能是最好的,其次MapStruct也是通过预编译完成,效率等同手动set,但是这两种相较于一些工具类稍微麻烦一些,一些常用的工具类方便简单,而且效率也相对不错,比如SpringBeanUtils,CgLib,hutoolBeanUtil效率功能都很不错,而且没有第三方依赖,非常干净好用,而Apache的BeanUtils与PropertyUtils虽然使用方便,但是效率不高,很少使用了。

对于手动set实现对象拷贝功能介绍一款idea插件可自动完成两个对象之间set/get值,GenerateAllSetter,IDEA 一键生成所有setter方法,alt+ctrl+s 快捷键进入设置,在Plugins中搜索GenerateAllSetter安装插件,然后重启idea即可。
使用方法,光标置于对象要转换的对象之上,alt+enter会出现快捷选项Generate all setter即可。

2. 对象属性拷贝工具

2.1 拷贝工具对比

拷贝工具

使用效率

Spring BeanUtils

使用方便,效率中等

Cglib BeanCopier

使用方便,效率最高

Apache BeanUtils

使用方便,效率低,原因Apache BeanUtils力求做的完美,做了很多校验,兼容,日志打印等导致性能下降。

Apache PropertyUtils

使用方便,效率低

Hutool BeanUtil

使用方便,封装完善,效率较高

2.2 拷贝工具验证

对比结果,仅供参考,当然耗时因素很多,机器配置环境等。

工具类

100次消耗时间

1000次消耗时间

10000次消耗时间

100000次消耗时间

1000000次消耗时间

Spring BeanUtils

307

14

37

165

1518

Cglib BeanCopier

32

3

10

27

80

Apache BeanUtils

28

37

184

767

7076

Apache PropertyUtils

2

16

101

857

8693

3. 对象属性拷贝实现

3.1 集合对象拷贝

BeanUtils使用instantiateClass初始化对象注意:
必须保证初始化类必须有public默认无参数构造器,注意初始化内部类时,内部类必须是静态的,否则报错!

import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;

import java.util.List;

/**
 * 对象属性拷贝
 *
 * @author zrj
 * @since 2021/7/29
 **/
@Slf4j
public class BeanUtil {
    /**
     * 对象属性拷贝
     * 将源对象的属性拷贝到目标对象
     *
     * @param source 源对象
     * @param target 目标对象
     */
    public static void copyProperties(Object source, Object target) {
        try {
            BeanUtils.copyProperties(source, target);
        } catch (BeansException e) {
            log.error("BeanUtil property copy  failed :BeansException", e);
        } catch (Exception e) {
            log.error("BeanUtil property copy failed:Exception", e);
        }
    }

    /**
     * @param input 输入集合
     * @param clzz  输出集合类型
     * @param <E>   输入集合类型
     * @param <T>   输出集合类型
     * @return 返回集合
     */
    public static <E, T> List<T> convertList2List(List<E> input, Class<T> clzz) {
        List<T> output = Lists.newArrayList();
        if (CollectionUtil.isEmpty(input)) {
            return output;
        }

        input.forEach(source -> {
            T target = BeanUtils.instantiateClass(clzz);
            BeanUtils.copyProperties(source, target);
            output.add(target);
        });
        return output;
    }
}

3.2 对象拷贝验证

BeanMapperUtils

package com.jerry.unit.bean;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.core.Converter;

import java.util.ArrayList;
import java.util.List;

/**
 * 对象属性拷贝工具
 * 1、Spring BeanUtils:效率其次
 * 2、Cglib BeanCopier:效率最高
 * 3、Apache BeanUtils:效率低,原因Apache BeanUtils力求做的完美,做了很多校验,兼容,日志打印等导致性能下降。
 * 4、Apache PropertyUtils:效率低
 * 5、HutoolBeanUtil :使用方便,效率比较高
 *
 * @author zrj
 * @since 2022/3/26
 **/
@Slf4j
public class BeanMapperUtils {
    /**
     * SpringBeanUtils对象属性拷贝
     *
     * @param source 源对象
     * @param target 目标对象
     */
    public static void mappingBeanBySpringBeanUtils(Object source, Object target) {
        try {
            BeanUtils.copyProperties(source, target);
        } catch (Exception e) {
            log.error("SpringBeanUtils Mapping Failed,ErrorMessage:{},{}", e.getMessage(), e);
        }
    }

    /**
     * CglibBeanCopier对象属性拷贝
     *
     * @param source       源对象,PersonDO.class
     * @param target       目标对象,PersonDTO.class
     * @param useConverter
     * @param var1         源对象,personDO
     * @param var2         目标对象,new PersonDTO()
     * @param var3
     */
    public static void mappingBeanByCglibBeanCopier(Class source, Class target, boolean useConverter, Object var1, Object var2, Converter var3) {
        try {
            BeanCopier copier = BeanCopier.create(source, target, useConverter);
            copier.copy(var1, var2, var3);
        } catch (Exception e) {
            log.error("CglibBeanCopier Mapping Failed,ErrorMessage:{},{}", e.getMessage(), e);
        }
    }

    /**
     * ApacheBeanUtils对象属性拷贝
     *
     * @param dest 目标对象
     * @param orig 源对象
     */
    private void mappingBeanByApacheBeanUtils(Object dest, Object orig) {
        try {
            org.apache.commons.beanutils.BeanUtils.copyProperties(dest, orig);
        } catch (Exception e) {
            log.error("ApacheBeanUtils Mapping Failed,ErrorMessage:{},{}", e.getMessage(), e);
        }
    }

    /**
     * ApachePropertyUtils进行属性拷贝(几乎很少使用)
     *
     * @param dest 目标对象
     * @param orig 源对象
     */
    private void mappingBeanByApachePropertyUtils(Object dest, Object orig) {
        try {
            org.apache.commons.beanutils.PropertyUtils.copyProperties(dest, orig);
        } catch (Exception e) {
            log.error("ApachePropertyUtils Mapping Failed,ErrorMessage:{},{}", e.getMessage(), e);
        }
    }

    /**
     * HutoolBeanUtil进行属性拷贝(几乎很少使用)
     *
     * @param source     源Bean对象
     * @param target     目标Bean对象
     * @param ignoreCase 是否忽略大小写
     */
    private void mappingBeanByHutoolBeanUtil(Object source, Object target, boolean ignoreCase) {
        try {
            BeanUtil.copyProperties(source, target, ignoreCase);
        } catch (Exception e) {
            log.error("HutoolBeanUtil Mapping Failed,ErrorMessage:{},{}", e.getMessage(), e);
        }
    }

    /**
     * SpringBeanUtils集合对象属性拷贝
     *
     * @param input 输入集合
     * @param clzz  输出集合类型
     * @param <E>   输入集合类型
     * @param <T>   输出集合类型
     * @return 返回集合
     */
    public static <E, T> List<T> mappingListSpringBeanUtils(List<E> input, Class<T> clzz) {
        List<T> output = new ArrayList<>();
        if (CollectionUtil.isEmpty(input)) {
            return output;
        }
        input.forEach(source -> {
            T target = BeanUtils.instantiateClass(clzz);
            BeanUtils.copyProperties(source, target);
            output.add(target);
        });
        return output;
    }
}

BeanMapperUtilsTest

package com.jerry.unit.bean;

import cn.hutool.core.bean.BeanUtil;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.util.StopWatch;

import java.lang.reflect.InvocationTargetException;

import static org.apache.commons.beanutils.BeanUtils.copyProperties;

/**
 * 对象属性拷贝工具测试类
 * 1、Spring BeanUtils:效率其次
 * 2、Cglib BeanCopier:效率最高
 * 3、Apache BeanUtils:效率低,原因Apache BeanUtils力求做的完美,做了很多校验,兼容,日志打印等导致性能下降。
 * 4、Apache PropertyUtils:效率低
 * 5、Dozer
 * 使用场景:DO,DTO,VO,PO,BO等场景装换的时候通过对象属性拷贝提高开发效率,简化代码,
 * 但是同时也带来了隐藏拷贝了那些属性,没有直接转换更为直接,按需使用吧
 *
 * @author zrj
 * @since 2022/3/26
 **/
public class BeanMapperUtilsTest {
    public static void main(String[] args) throws Exception {
        //构建对象
        PersonDO personDO = PersonDO.builder().id(20220326).name("jerry").age(6).build();

        BeanMapperUtilsTest mapperTest = new BeanMapperUtilsTest();

        System.out.println("------mappingBySelf-------");
        mapperTest.mappingBySelf(personDO, 100);
        mapperTest.mappingBySelf(personDO, 1000);
        mapperTest.mappingBySelf(personDO, 10000);
        mapperTest.mappingBySelf(personDO, 100000);
        mapperTest.mappingBySelf(personDO, 1000000);

        System.out.println("------mappingBeanByHutoolBeanUtil-------");
        mapperTest.mappingBeanByHutoolBeanUtil(personDO, 100);
        mapperTest.mappingBeanByHutoolBeanUtil(personDO, 1000);
        mapperTest.mappingBeanByHutoolBeanUtil(personDO, 10000);
        mapperTest.mappingBeanByHutoolBeanUtil(personDO, 100000);
        mapperTest.mappingBeanByHutoolBeanUtil(personDO, 1000000);

        System.out.println("------mappingBySpringBeanUtils-------");
        mapperTest.mappingBySpringBeanUtils(personDO, 100);
        mapperTest.mappingBySpringBeanUtils(personDO, 1000);
        mapperTest.mappingBySpringBeanUtils(personDO, 10000);
        mapperTest.mappingBySpringBeanUtils(personDO, 100000);
        mapperTest.mappingBySpringBeanUtils(personDO, 1000000);

        System.out.println("------mappingByCglibBeanCopier-------");
        mapperTest.mappingByCglibBeanCopier(personDO, 100);
        mapperTest.mappingByCglibBeanCopier(personDO, 1000);
        mapperTest.mappingByCglibBeanCopier(personDO, 10000);
        mapperTest.mappingByCglibBeanCopier(personDO, 100000);
        mapperTest.mappingByCglibBeanCopier(personDO, 1000000);

        System.out.println("------mappingByApachePropertyUtils-------");
        mapperTest.mappingByApachePropertyUtils(personDO, 100);
        mapperTest.mappingByApachePropertyUtils(personDO, 1000);
        mapperTest.mappingByApachePropertyUtils(personDO, 10000);
        mapperTest.mappingByApachePropertyUtils(personDO, 100000);
        mapperTest.mappingByApachePropertyUtils(personDO, 1000000);

        System.out.println("------mappingByApacheBeanUtils-------");
        mapperTest.mappingByApacheBeanUtils(personDO, 100);
        mapperTest.mappingByApacheBeanUtils(personDO, 1000);
        mapperTest.mappingByApacheBeanUtils(personDO, 10000);
        mapperTest.mappingByApacheBeanUtils(personDO, 100000);
        mapperTest.mappingByApacheBeanUtils(personDO, 1000000);
    }

    /**
     * 手动进行属性拷贝
     */
    private void mappingBySelf(PersonDO personDO, int times) {
        StopWatch stopwatch = new StopWatch();
        stopwatch.start();

        for (int i = 0; i < times; i++) {
            PersonDTO personDTO = new PersonDTO();
            personDTO.setName(personDO.getName());
            personDTO.setAge(personDO.getAge());
        }

        stopwatch.stop();
        System.out.println("mappingBySelf cost " + times + " :" + stopwatch.getTotalTimeMillis());
    }

    /**
     * 手动进行属性拷贝
     */
    private void mappingBeanByHutoolBeanUtil(PersonDO personDO, int times) {
        StopWatch stopwatch = new StopWatch();
        stopwatch.start();

        for (int i = 0; i < times; i++) {
            PersonDTO personDTO = new PersonDTO();
            BeanUtil.copyProperties(personDO, personDTO, false);
        }

        stopwatch.stop();
        System.out.println("mappingBeanByHutoolBeanUtil cost " + times + " :" + stopwatch.getTotalTimeMillis());
    }

    /**
     * Spring BeanUtils进行属性拷贝
     */
    private void mappingBySpringBeanUtils(PersonDO personDO, int times) {
        StopWatch stopwatch = new StopWatch();
        stopwatch.start();

        for (int i = 0; i < times; i++) {
            PersonDTO personDTO = new PersonDTO();
            BeanUtils.copyProperties(personDO, personDTO);
        }

        stopwatch.stop();
        System.out.println("mappingBySpringBeanUtils cost " + times + " :" + stopwatch.getTotalTimeMillis());
    }

    /**
     * Cglib BeanCopier进行属性拷贝
     */
    private void mappingByCglibBeanCopier(PersonDO personDO, int times) {
        StopWatch stopwatch = new StopWatch();

        stopwatch.start();

        for (int i = 0; i < times; i++) {
            PersonDTO personDTO = new PersonDTO();
            BeanCopier copier = BeanCopier.create(PersonDO.class, PersonDTO.class, false);
            copier.copy(personDO, personDTO, null);
        }

        stopwatch.stop();

        System.out.println("mappingByCglibBeanCopier cost " + times + " :" + stopwatch.getTotalTimeMillis());
    }

    /**
     * Apache BeanUtils进行属性拷贝
     */
    private void mappingByApacheBeanUtils(PersonDO personDO, int times)
            throws InvocationTargetException, IllegalAccessException {
        StopWatch stopwatch = new StopWatch();
        stopwatch.start();
        for (int i = 0; i < times; i++) {
            PersonDTO personDTO = new PersonDTO();
            copyProperties(personDTO, personDO);
        }
        stopwatch.stop();
        System.out.println("mappingByApacheBeanUtils cost " + times + " :" + stopwatch.getTotalTimeMillis());
    }

    /**
     * Apache PropertyUtils进行属性拷贝
     * 无人问津
     */
    private void mappingByApachePropertyUtils(PersonDO personDO, int times)
            throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        StopWatch stopwatch = new StopWatch();
        stopwatch.start();
        for (int i = 0; i < times; i++) {
            PersonDTO personDTO = new PersonDTO();
            PropertyUtils.copyProperties(personDTO, personDO);
        }
        stopwatch.stop();
        System.out.println("mappingByApachePropertyUtils cost " + times + " :" + stopwatch.getTotalTimeMillis());
    }
}

PersonDO & PersonDTO

/**
 * 人员数据对象
 *
 * @author zrj
 * @since 2022/3/26
 **/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PersonDO {
    private Integer id;
    private String name;
    private Integer age;
}

/**
 * 人员传输对象
 *
 * @author zrj
 * @since 2022/3/26
 **/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PersonDTO {
    private String name;
    private Integer age;
}