一、引言
常用的拷贝方式:
- 手动get&set实现。
- 通过Cglib的BeanCopier实现。
- 通过Apache的BeanUtils实现。
- 通过Apache的PropertyUtils实现。
- 通过Spring的BeanUtils实现。
(此模块先对常用拷贝方式进行总结,后文会详细介绍深拷贝&浅拷贝的区别以及实现方式。)
二.常用拷贝方式之间的区别
1. 手动get&set实现:
简单,粗暴,高性能
2. 通过Cglib的BeanCopier实现(使用动态代理,效率高):
不使用类型转换器:
BeanCopier beanCopier = BeanCopier.create(SourceClass.class, TargetClass.class, false); 前赋后
beanCopier.copy(sourceClass, targetClass, null);
不支持不同类型字段的转换,但可通过自己实现的converter转换器实现类型之间的转换。
使用类型转换器:
BeanCopier beanCopier = BeanCopier.create(SourceClass.class, TargetClass.class, true); 前赋后
beanCopier.copy(sourceClass, targetClass, converter); //converter为自己实现的转换器。
例如:
public class Long2StringConvert implements Converter {
@Override
public Object convert(Object sourceValue, Class targetClass, Object methodName) {
if( null != sourceValue && sourceValue.getClass().equals(Long.class) && targetClass.equals(String.class)) {
return String.valueOf(sourceValue);
}
return sourceValue;
}
}
生成用于复制两个类的BeanCoper类:
public final static BeanCopier dir2DirVOCopier = BeanCopier.create(Dir.class,DirVO.class,true);
执行拷贝:
BeanCopyProvider.dir2DirVOCopier.copy(dir, vo, new Long2StringConvert());
参考文档:
CGLIB中BeanCopier源码实现 - 简书 // CGLIB中BeanCopier源码实现
对象拷贝类PropertyUtils,BeanUtils,BeanCopier的技术沉淀(1)------功能简介_express_wind的专栏-CSDN博客
对象拷贝类PropertyUtils,BeanUtils,BeanCopier的技术沉淀(2)------缺陷预防_express_wind的专栏-CSDN博客
3. 通过Apache的BeanUtils实现(反射机制):
使用:BeanUtils.copyProperties(targetClass, sourceClass); 后赋前
提供自动转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换。
BeanUtils支持的转换类型如下:
* java.lang.BigDecimal
* java.lang.BigInteger
* boolean and java.lang.Boolean
* byte and java.lang.Byte
* char and java.lang.Character
* java.lang.Class
* double and java.lang.Double
* float and java.lang.Float
* int and java.lang.Integer
* long and java.lang.Long
* short and java.lang.Short
* java.lang.String
* java.sql.Date
* java.sql.Time
* java.sql.Timestamp
这里要注意一点,java.util.Date是不被支持的,而它的子类java.sql.Date是被支持的。因此如果对象包含时间类型的属性,且注意被转换的时候,一定要使用java.sql.Date类型。否则在转换时会提示argument mistype异常。
可使用转换器beanutils.Converter,增强apache的beanUtils的拷贝属性,注册一些新的类型转换。
例:
ConvertUtils.register(new DateConvert(), java.util.Date.class);
4. 通过Apache的PropertyUtils实现(反射机制):
使用:PropertyUtils.copyProperties(targetClass, sourceClass); 后赋前
不提供自动转换功能。当类型不一致时会抛出异常。
5. 通过Spring的BeanUtils实现(反射机制):
使用:BeanUtils.copyProperties(sourceClass,targetClass); 前赋后
BeanUtils.copyProperties(sourceClass,targetClass,ignoreProperties);
不支持不同类型之间的转换。
三、性能测试
测试代码:
Data.java:
@ToString
@lombok.Data
public class Data {
private String aa;
private String ab;
private String ac;
private String ad;
private String ae;
private String af;
private String ag;
private Map map;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
PerformanceTesting.java:
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.cglib.beans.BeanCopier;
import java.util.HashMap;
import java.util.Map;
public class PerformanceTesting {
public static void main(String[] args) {
try {
//性能测试
Data sourceClass = new Data();
sourceClass.setAa("a");
sourceClass.setAb("b");
sourceClass.setAc("c");
sourceClass.setAd("d");
sourceClass.setAe("e");
sourceClass.setAf("f");
sourceClass.setAg("g");
sourceClass.setAd("d");
sourceClass.setAd("d");
Map<String, String> map = new HashMap<String, String>();
map.put("test1", "test1");
map.put("test2", "test2");
map.put("test3", "test3");
sourceClass.setMap(map);
BeanCopier bc = BeanCopier.create(Data.class, Data.class, false);
int number = 10000;
System.out.println("\n---- number = " + number + " ----\n");
//beanCoper
long time0 = System.currentTimeMillis();
for(int i = 0; i < number; i++) {
beanCopier(sourceClass, bc);
}
System.out.println("Cglib BeanCopier :: " + (System.currentTimeMillis()-time0));
//get & set
long time1 = System.currentTimeMillis();
for(int i = 0; i < number; i++) {
getAndSet(sourceClass);
}
System.out.println("getAndSet :: " + (System.currentTimeMillis()-time1));
//apache Properties
long time2 = System.currentTimeMillis();
for(int i = 0; i < number; i++) {
propertyUtils(sourceClass);
}
System.out.println("apache PropertyUtils :: " + (System.currentTimeMillis()-time2));
//apache BeanUtils
long time3 = System.currentTimeMillis();
for(int i = 0; i < number; i++) {
apacheBeanUtils(sourceClass);
}
System.out.println("apache BeanUtils :: " + (System.currentTimeMillis()-time3));
//Spring BeanUtils
long time4 = System.currentTimeMillis();
for(int i = 0; i < number; i++) {
springBeanUtils(sourceClass);
}
System.out.println("spring BeanUtils :: " + (System.currentTimeMillis()-time4));
}catch (Exception e) {
e.printStackTrace();
}
}
public static void beanCopier(Data sourceClass, BeanCopier bc) {
Data targetClass = new Data();
bc.copy(sourceClass, targetClass, null);
}
public static void getAndSet(Data sourceClass) {
Data targetClass = new Data();
targetClass.setAa(sourceClass.getAa());
targetClass.setAb(sourceClass.getAb());
}
public static void springBeanUtils(Data sourceClass) {
Data targetClass = new Data();
org.springframework.beans.BeanUtils.copyProperties(sourceClass, targetClass); //前赋后
}
public static void apacheBeanUtils(Data sourceClass) throws Exception {
Data targetClass = new Data();
BeanUtils.copyProperties(targetClass, sourceClass); //后赋前
}
public static void propertyUtils(Data sourceClass) throws Exception {
Data targetClass = new Data();
PropertyUtils.copyProperties(targetClass, sourceClass); //后赋前
}
}
测试结果:




| 100次 | 1000次 | 1万次 | 1亿次 | |
| Cglib BeanCopier | 0 | 0 | 3 | 15 |
| getAndSet | 0 | 0 | 0 | 7 |
| apache PropertyUtils | 85 | 106 | 216 | 386233 |
| apache BeanUtils | 11 | 40 | 179 | 668751 |
| spring BeanUtils | 80 | 63 | 86 | 33286 |
结论:
- 综上,性能测试结果:getAndSet > Cglib BeanCopier > apache BeanUtils > spring BeanUtils > apache PropertyUtils
- bean copy场景较少或者对性能要求较高的部分避免使用任何bean copy框架
- 如果要使用bean copy框架,优先使用cglib。同时需要注意:cglib使用BeanCopier.create()也是非常耗时,避免多次调用,尽可能做成全局初始化一次
四.遗留问题:
目标类set方法返回值不为void时,BeanCoper无法拷贝。


后在BeanCopier#generateClass()方法中找到答案:
public void generateClass(ClassVisitor v) {
Type sourceType = Type.getType(this.source);
Type targetType = Type.getType(this.target);
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(46, 1, this.getClassName(), BeanCopier.BEAN_COPIER, (Type[])null, "<generated>");
EmitUtils.null_constructor(ce);
CodeEmitter e = ce.begin_method(1, BeanCopier.COPY, (Type[])null);
PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(this.source);
PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(this.target);
Map names = new HashMap();
for(int i = 0; i < getters.length; ++i) {
names.put(getters[i].getName(), getters[i]);
}
Local targetLocal = e.make_local();
Local sourceLocal = e.make_local();
if (this.useConverter) {
e.load_arg(1);
e.checkcast(targetType);
e.store_local(targetLocal);
e.load_arg(0);
e.checkcast(sourceType);
e.store_local(sourceLocal);
} else {
e.load_arg(1);
e.checkcast(targetType);
e.load_arg(0);
e.checkcast(sourceType);
}
for(int i = 0; i < setters.length; ++i) {
PropertyDescriptor setter = setters[i];
PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());
if (getter != null) {
MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
if (this.useConverter) {
Type setterType = write.getSignature().getArgumentTypes()[0]; //获取参数类型
e.load_local(targetLocal);
e.load_arg(2);
e.load_local(sourceLocal);
e.invoke(read);
e.box(read.getSignature().getReturnType());
EmitUtils.load_class(e, setterType);
e.push(write.getSignature().getName());
e.invoke_interface(BeanCopier.CONVERTER, BeanCopier.CONVERT);
e.unbox_or_zero(setterType);
e.invoke(write);
} else if (compatible(getter, setter)) {
e.dup2();
e.invoke(read);
e.invoke(write);
}
}
}
e.return_value();
e.end_method();
ce.end_class();
}
private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {
return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());
}
public native boolean isAssignableFrom(Class<?> cls); //校验返回值是否与方法名匹配
五.浅拷贝&深拷贝
一般使用 『 = 』号做赋值操作的时候,对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。
而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。
1、浅拷贝
对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。

2、深拷贝
对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

经验证,BeanCoper和Spring的BeanUtils在拷贝对象时使用的是浅拷贝。
测试代码如下:
TestData.java:
import com.bj58.beanCoper.Data;
import java.util.Map;
import lombok.ToString;
@ToString
@lombok.Data
public class TestData {
private Data data;
private Map map;
private String string;
}
CopyTest.java:
import com.bj58.beanCoper.Data;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.beans.BeanCopier;
import java.util.HashMap;
import java.util.Map;
public class CopyTest {
private final static BeanCopier beanCopier = BeanCopier.create(TestData.class, TestData.class, false);
public static void main(String[] args) {
TestData sourceClass = new TestData();
final Map<String, String> map = new HashMap<String, String>();
map.put("test1", "test1");
map.put("test2", "test2");
map.put("test3", "test3");
sourceClass.setMap(map);
sourceClass.setString("test");
sourceClass.setData(new Data(){{
setAa("11");
setAb("11");
setAc("11");
setAd("11");
setAe("11");
setAf("11");
setAg("11");
setMap(map);
}});
// beanCoperCopy
TestData beanCoperTargetClass = beanCoperCopy(sourceClass);
System.out.println(beanCoperTargetClass);
System.out.println("直接使用beanCopier赋值后,sourceClass.getData() == beanCoperTargetClass.getData()值为 " + (sourceClass.getData() == beanCoperTargetClass.getData()));
// beanUtilsCopy
TestData beanUtilsTargetClass = beanUtilsCopy(sourceClass);
System.out.println(beanUtilsTargetClass);
System.out.println("直接使用beanUtils赋值后,sourceClass.getData() == beanUtilsTargetClass.getData()值为 " + (sourceClass.getData() == beanUtilsTargetClass.getData()));
System.out.println("\n --------- 修改源对象数据 ----------");
System.out.println("修改前 sourceClass.getData().getAg()值为 " + sourceClass.getData().getAg());
sourceClass.getData().setAg("21");
System.out.println("修改后 sourceClass.getData().getAg()值为 " + sourceClass.getData().getAg());
System.out.println("修改后 beanCoperTargetClass.getData().getAg()值为 " + beanCoperTargetClass.getData().getAg());
System.out.println("修改后 beanUtilsTargetClass.getData().getAg()值为 " + beanUtilsTargetClass.getData().getAg());
System.out.println("修改了sourceClass的data值后,sourceClass.getData() == beanCoperTargetClass.getData()值为 " + (sourceClass.getData() == beanCoperTargetClass.getData()));
System.out.println("修改了sourceClass的data值后,sourceClass.getData() == beanUtilsTargetClass.getData()值为 " + (sourceClass.getData() == beanUtilsTargetClass.getData()));
}
private static TestData beanCoperCopy(TestData sourceClass) {
TestData targetClass = new TestData();
beanCopier.copy(sourceClass, targetClass, null);
return targetClass;
}
private static TestData beanUtilsCopy(TestData sourceClass) {
TestData targetClass = new TestData();
BeanUtils.copyProperties(sourceClass, targetClass);
return targetClass;
}
}
运行结果:

对象深拷贝的实现
- 实现Cloneable接口,重写对象的clone()方法
需要注意的点:
1)代码侵入较大
2)默认的clone()方法是浅拷贝,深拷贝是需要自己来实现的。对于基本类型可以直接赋值,而对于对象、容器、数组来讲,需要创建一个新的出来,然后重新赋值
3)Java Cloneable接口实际上是个空接口,没有任何方法,实际的clone()是object的方法,但是是一个protected的方法,因此需要重写这个方法,并且声明为public,不然的话protected方法在其它包中是无法访问的。
4)虽然Cloneable接口是个空接口,但JVM会作检查,如果不实现Cloneable接口则会抛出java.lang.CloneNotSupportedException异常。
2.使用序列化
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以通过将当前对象序列化,然后再反序列化的方式来实现对象的深克隆。
测试代码:
import com.bj58.beanCoper.Data;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class DeepCopy {
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
// 创建原始的可序列化对象
TestData sourceClass = new TestData();
final Map<String, String> map = new HashMap<String, String>();
map.put("test1", "test11");
map.put("test2", "test22");
map.put("test3", "test33");
sourceClass.setMap(map);
sourceClass.setString("test");
sourceClass.setData(new Data(){{
setAa("11");
setAb("11");
setAc("121");
setAd("11");
setAe("11");
setAf("11");
setAg("11");
setMap(map);
}});
System.out.println("sourceClass = " + sourceClass);
TestData targetClass = null;
// 通过序列化实现深拷贝
ByteArrayOutputStream bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
// 序列化以及传递这个对象
oos.writeObject(sourceClass);
oos.flush();
ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bin);
// 返回新的对象
targetClass = (TestData) ois.readObject();
System.out.println("targetClass = " + targetClass);
// 校验内容是否相同
System.out.println("sourceClass.getData() == targetClass.getData() ? " + (sourceClass.getData() == targetClass.getData()));
// 改变原始对象的内容
System.out.println("\n ----------------- 修改原始对象内容 ------------------");
sourceClass.getData().setAg("12313");
// 查看每一个现在的内容
System.out.println("修改了原始对象后 sourceClass.getData().getAg() = " + sourceClass.getData().getAg());
System.out.println("修改了原始对象后 targetClass.getData().getAg() = " + targetClass.getData().getAg());
} catch (Exception e) {
e.printStackTrace();
} finally {
if(oos != null){
oos.close();
}
if(ois != null){
ois.close();
}
}
}
}
测试结果:

本文详细介绍了Java中对象拷贝的各种方式,包括手动get&set、Cglib的BeanCopier、Apache的BeanUtils、PropertyUtils以及Spring的BeanUtils。对比了它们之间的性能和区别,指出Cglib BeanCopier在性能上表现优秀。此外,文章讨论了浅拷贝和深拷贝的概念,解释了默认赋值操作为浅拷贝,并提供了实现深拷贝的两种方法:实现Cloneable接口和使用序列化。通过代码示例展示了这两种深拷贝的实现方式。

399

被折叠的 条评论
为什么被折叠?



