Bean的拷贝之BeanUtils
本文来说下Bean的各类拷贝工具
文章目录
- 概述
- 对象拷贝
- BeanUtils
- apache的BeanUtils
- spring的BeanUtils
- cglib BeanCopier
- Hutool BeanUtil
- Mapstruct
- 本文小结
概述
在我们实际项目开发过程中,我们经常需要将不同的两个对象实例进行属性复制,从而基于源对象的属性信息进行后续操作,而不改变源对象的属性信息,比如DTO数据传输对象和数据对象DO,我们需要将DO对象进行属性复制到DTO,但是对象格式又不一样,所以我们需要编写映射代码将对象中的属性值从一种类型转换成另一种类型。
这种转换最原始的方式就是手动编写大量的 get/set代码,当然这是我们开发过程不愿意去做的,因为它确实显得很繁琐。为了解决这一痛点,就诞生了一些方便的类库,常用的有 apache的 BeanUtils,spring的 BeanUtils, Dozer,Orika等拷贝工具。
对象拷贝
在具体介绍各种BeanUtils之前,先来补充一些基础知识。它们本质上就是对象拷贝工具,而对象拷贝又分为深拷贝和浅拷贝,下面进行详细解释。
什么是浅拷贝和深拷贝
浅拷贝只会拷贝引用本身,而深拷贝还会拷贝引用所指向的数据信息
在Java中,除了 基本数据类型 之外,还存在 类的实例对象 这个引用数据类型,而一般使用 “=”号做赋值操作的时候,对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际还是指向的同一个对象。
而浅拷贝和深拷贝就是在这个基础上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝 。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝 。
简单来说:
浅拷贝 :对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
深拷贝 :对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
BeanUtils
前面简单讲了一下对象拷贝的一些知识,下面就来具体看下常用的BeanUtils工具
apache的BeanUtils
Address类
package cn.wideth.util;import lombok.AllArgsConstructor;
import lombok.Data;@Data
@AllArgsConstructor
public class Address {private String address;
}
user对象实体
package cn.wideth.util;import lombok.AllArgsConstructor;
import lombok.Data;@Data
@AllArgsConstructor
public class PersonInfo {private Integer id;private String username;private Address address;
}
user对象拷贝实体
package cn.wideth.util;import lombok.Data;@Data
public class PersonCopy {private Integer id;private String username;private Address address;
}
测试程序
package cn.wideth.util;import org.apache.commons.beanutils.BeanUtils;public class TestApacheBeanUtils {public static void main(String[] args) {//下面只是用于单独测试PersonInfo pi = new PersonInfo(1, "jack", new Address("北京"));PersonCopy pc = new PersonCopy();try {BeanUtils.copyProperties(pc,pi);} catch (Exception e) {e.printStackTrace();}System.out.println("pi: " + pi);System.out.println("pc: "+ pc);System.out.println("-------变更值以后----------");pi.setId(10);pi.setUsername("hello");pi.getAddress().setAddress("上海");System.out.println("pi: " + pi);System.out.println("pc: "+ pc);}
}
测试结果
从测试程序中,可以看到apache的BeanUtils是一种浅拷贝。
从上面的例子可以看出,对象拷贝非常简单,BeanUtils最常用的方法就是:
//将源对象中的值拷贝到目标对象
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
默认情况下,使用org.apache.commons.beanutils.BeanUtils对复杂对象的复制是引用,这是一种浅拷贝。
但是由于 Apache下的BeanUtils对象拷贝性能太差,不建议使用,而且在阿里巴巴Java开发规约插件 上也明确指出避免用Apache Beanutils进行属性的copy。
commons-beantutils 对于对象拷贝加了很多的检验,包括类型的转换,甚至还会检验对象所属的类的可访问性,可谓相当复杂,这也造就了它的差劲的性能,具体实现代码如下:
public void copyProperties(final Object dest, final Object orig)throws IllegalAccessException, InvocationTargetException {// Validate existence of the specified beansif (dest == null) {throw new IllegalArgumentException("No destination bean specified");}if (orig == null) {throw new IllegalArgumentException("No origin bean specified");}if (log.isDebugEnabled()) {log.debug("BeanUtils.copyProperties(" + dest + ", " +orig + ")");}// Copy the properties, converting as necessaryif (orig instanceof DynaBean) {final DynaProperty[] origDescriptors =((DynaBean) orig).getDynaClass().getDynaProperties();for (DynaProperty origDescriptor : origDescriptors) {final String name = origDescriptor.getName();// Need to check isReadable() for WrapDynaBean// (see Jira issue# BEANUTILS-61)if (getPropertyUtils().isReadable(orig, name) &&getPropertyUtils().isWriteable(dest, name)) {final Object value = ((DynaBean) orig).get(name);copyProperty(dest, name, value);}}} else if (orig instanceof Map) {@SuppressWarnings("unchecked")final// Map properties are always of type <String, Object>Map<String, Object> propMap = (Map<String, Object>) orig;for (final Map.Entry<String, Object> entry : propMap.entrySet()) {final String name = entry.getKey();if (getPropertyUtils().isWriteable(dest, name)) {copyProperty(dest, name, entry.getValue());}}} else /* if (orig is a standard JavaBean) */ {final PropertyDescriptor[] origDescriptors =getPropertyUtils().getPropertyDescriptors(orig);for (PropertyDescriptor origDescriptor : origDescriptors) {final String name = origDescriptor.getName();if ("class".equals(name)) {continue; // No point in trying to set an object's class}if (getPropertyUtils().isReadable(orig, name) &&getPropertyUtils().isWriteable(dest, name)) {try {final Object value =getPropertyUtils().getSimpleProperty(orig, name);copyProperty(dest, name, value);} catch (final NoSuchMethodException e) {// Should not happen}}}}}
spring的BeanUtils
使用spring的BeanUtils进行对象拷贝:
package cn.wideth.util;import org.springframework.beans.BeanUtils;public class TestSpringBeanUtils {public static void main(String[] args) {//下面只是用于单独测试PersonInfo pi = new PersonInfo(2, "tom", new Address("杭州"));PersonCopy pc = new PersonCopy();BeanUtils.copyProperties(pi,pc);System.out.println("pi: " + pi);System.out.println("pc: "+ pc);System.out.println("-------变更值以后----------");pi.setId(16);pi.setUsername("hello");pi.getAddress().setAddress("苏州");System.out.println("pi: " + pi);System.out.println("pc: "+ pc);}
}
程序结果
从测试程序中,可以看到spring的BeanUtils是一种浅拷贝。
spring下的BeanUtils也是使用 copyProperties方法进行拷贝,只不过它的实现方式非常简单,就是对两个对象中相同名字的属性进行简单的get/set,仅检查属性的可访问性。具体实现如下:
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,@Nullable String... ignoreProperties) throws BeansException {Assert.notNull(source, "Source must not be null");Assert.notNull(target, "Target must not be null");Class<?> actualEditable = target.getClass();if (editable != null) {if (!editable.isInstance(target)) {throw new IllegalArgumentException("Target class [" + target.getClass().getName() +"] not assignable to Editable class [" + editable.getName() + "]");}actualEditable = editable;}PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);for (PropertyDescriptor targetPd : targetPds) {Method writeMethod = targetPd.getWriteMethod();if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());if (sourcePd != null) {Method readMethod = sourcePd.getReadMethod();if (readMethod != null &&ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {try {if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {readMethod.setAccessible(true);}Object value = readMethod.invoke(source);if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {writeMethod.setAccessible(true);}writeMethod.invoke(target, value);}catch (Throwable ex) {throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", ex);}}}}}}
可以看到,成员变量赋值是基于目标对象的成员列表,并且会跳过ignore的以及在源对象中不存在,所以这个方法是安全的,不会因为两个对象之间的结构差异导致错误,但是必须保证同名的两个成员变量类型相同。
cglib BeanCopier
maven导入
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
使用cglib BeanCopier进行对象拷贝:
package cn.wideth.util;import org.springframework.cglib.beans.BeanCopier;public class TeatBeanCopier {public static void main(String[] args) {//下面只是用于单独测试PersonInfo pi = new PersonInfo(2, "tom", new Address("长春"));PersonCopy pc = new PersonCopy();BeanCopier beanCopier = BeanCopier.create(pi.getClass(),pc.getClass(), false);beanCopier.copy(pi,pc,null);System.out.println("pi: " + pi);System.out.println("pc: "+ pc);System.out.println("-------变更值以后----------");pi.setId(10);pi.setUsername("jack");pi.getAddress().setAddress("哈尔滨");System.out.println("pi: " + pi);System.out.println("pc: "+ pc);}
}
程序结果
在使用BeanCopier时,如果存在基本类型和包装类,是无法被正常拷贝,改为相同类型后才能被正常拷贝。另外,BeanCopier使用的仍然是浅拷贝,从测试程序中大家可以看出。
Hutool BeanUtil
hutool是个人平常使用比较频繁的一个工具包,对文件、加密解密、转码、正则、线程、XML等JDK方法进行封装,并且也可以进行对象的拷贝。在使用前引入坐标:
maven导入
<!-- hutool工具类-->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.3.6</version>
</dependency>
使用Hutool BeanUtil的测试程序
package cn.wideth.util;import cn.hutool.core.bean.BeanUtil;public class TestHutool {public static void main(String[] args) {//下面只是用于单独测试PersonInfo pi = new PersonInfo(2, "tom", new Address("厦门"));PersonCopy pc = new PersonCopy();BeanUtil.copyProperties(pi,pc);System.out.println("pi: " + pi);System.out.println("pc: "+ pc);System.out.println("-------变更值以后----------");pi.setId(23);pi.setUsername("jack");pi.getAddress().setAddress("福州");System.out.println("pi: " + pi);System.out.println("pc: "+ pc);}
}
程序结果
从程序结果,我们知道使用的也是浅拷贝方式。
Mapstruct
Mapstruct的使用和上面几种方式有些不同,因为上面的几种方式,spring和apache,hutool使用的都是反射,cglib是基于字节码文件的操作,都是在都代码运行期间动态执行的,但是Mapstruct不同,它在编译期间就生成了 Bean属性复制的代码,运行期间就无需使用反射或者字节码技术,所以具有很高的性能。
maven导入
<!--mapstruct--><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-jdk8</artifactId><version>1.3.0.Final</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.3.0.Final</version></dependency>
需要额外写一个接口来实现:
package cn.wideth.util;import org.mapstruct.Mapper;@Mapper
public interface ConvertMapper {PersonCopy infoToCopy(PersonInfo pi);
}
这里的@Mapper注解不是用于mybatis的注解,而是org.mapstruct.Mapper。使用起来也非常简单:
程序测试
package cn.wideth.util;import org.mapstruct.factory.Mappers;public class TestMapstruct {public static void main(String[] args) {//下面只是用于单独测试PersonInfo pi = new PersonInfo(2, "tom", new Address("成都"));ConvertMapper mapper = Mappers.getMapper(ConvertMapper.class);PersonCopy pc = mapper.infoToCopy(pi);System.out.println("pi: " + pi);System.out.println("pc: "+ pc);System.out.println("-------变更值以后----------");pi.setId(30);pi.setUsername("jack");pi.getAddress().setAddress("重庆");System.out.println("pi: " + pi);System.out.println("pc: "+ pc);}
}
程序结果
从测试程序可以看出Mapstruct依然是浅拷贝。
本文小结
本文介绍了几种常用的bean拷贝对象。
Bean的拷贝之BeanUtils相关推荐
- log4j2logback打印日志的效率问题【细节3】bean属性拷贝【细节4】
2019独角兽企业重金招聘Python工程师标准>>> 1).在打印日志时,我们可以使用下面的代码: logger.debug("Entry Number :"+ ...
- 【小工具】根据定义的白名单字段进行Bean的拷贝
背景 Bean的拷贝一直有一些类可以使用,比如Apache的org.apache.commons.beanutils.BeanUtils或者Spring的org.springframework.bea ...
- 对象拷贝 Apache BeanUtils与Spring BeanUtils性能比较
前言 在我们实际项目开发过程中,我们经常需要将不同的两个对象实例进行属性复制,从而基于源对象的属性信息进行后续操作,而不改变源对象的属性信息,比如DTO数据传输对象和数据对象DO,我们需要将DO对象进 ...
- 使用Cglib的BeanCopier实现Bean的拷贝
选择Cglib的BeanCopier进行Bean拷贝的理由是,其性能要比Spring的BeanUtils,Apache的BeanUtils和PropertyUtils要好很多,尤其是数据量比较大的情况 ...
- 性能篇之对象拷贝工具BeanUtils.copyProperties和BeanCopier.copy的比较
对象的拷贝在开发过程中肯定非常常见,想必大家使用spring中的BeanUtils.copyProperties来完成的,小编最初也是用习惯了这个工具,但是在一次codereview中,大佬给我提出建 ...
- mapstruct实体类拷贝替代beanutils
pom文件 <!-- 对象拷贝 --><dependency><groupId>org.mapstruct</groupId><artifactI ...
- Spring核心知识点总结
本文记录Spring全家桶核心知识点 文章目录 Spring总览 Spring IOC相关 Spring AOP相关 Spring总览 记录下spring总体相关的知识 题目 链接地址 Spring核 ...
- 如何对一个对象进行深拷贝
前面说了如何对一个对象进行浅拷贝,本文来说下如何对一个对象进行深拷贝. 文章目录 概述 拷贝对象 方法一 构造函数 方法二 重载clone()方法 方法三 Apache Commons Lang序列化 ...
- Bean对象的拷贝方法BeanCopier和BeanUtils
1. BeanCopier 默认只复制名称和类型相同的字段,对date为空的情况不进行复制.把相同的进行复制,把不同的,也就是需要我们个性化的一些字段,单独出来用set来赋值,这样程序就会很明确,重点 ...
最新文章
- 设计模式:设计模式七大原则之迪米特法则
- Java Comparable接口的陷阱
- 如何自动导出内存映像文件?
- 蔡工RK系列Android驱动开发入门视频课程
- [导入] [转]总结C++与C#的区别
- 【算法笔记】对两种线性基的理解
- 李航——《统计学习方法》(一)
- python播放音乐同步歌词_使用python播放音乐并制作LRC歌词文件
- 系统漏洞是威胁计算机网络安全的形式,系统漏洞是威胁计算机网络安全的形式_网络 安全 银行 漏洞 川企_https 安全受到 威胁(2)...
- 【保险类项目】开发必须了解知道的概念 / 术语
- sphinx(附demo)
- 数据库SQL实战 --44.将titles_test表名修改为titles_2017
- linux 模拟arm,拒绝开发板, 用 SkyEye 就可以模拟 ARM Linux!
- 八字-十天干、十二地支、六十甲子
- 让源代码成为开发者最宝贵的财富
- SQL 语句集合(行转列,参数化...)
- Javascript vue 数组中的对象分离 获取对象属性名称 对象属性值
- 会不会跟着AXIS2上贼船
- Spring源码分析(1) —— 从Xml的加载到解析
- java基础面试题总结(2023版)