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;
}