1、浅复制测试
浅复制和深复制最本质的区别就是引用数据类型的地址指向问题,见下方图解。
在本次测试中,在School类里定义Student类对象实现浅复制测试,School类代码定义如下:
public class School {
private Student student; //通过Student类对象模拟引用数据类型的地址指向
private String addr;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "School{" +
"student=" + student +
", addr='" + addr + '\'' +
'}';
}
}
Student类定义如下:
public class Student {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试Demo:
public void test02(){
Student student = new Student();
student.setAge(20);
student.setName("java");
School school = new School();
School school02 = new School();
try {
BeanUtils.setProperty(school,"student",student);
BeanUtils.setProperty(school,"addr","xxx路xxx号");
BeanUtils.copyProperties(school02,school);
System.out.println(school.getStudent() == school02.getStudent());
school02.getStudent().setName("java123456");
System.out.println(school);
System.out.println(school02);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
最终运行结果:
可以看到,当其中一个引用数据类型对象的addr属性发生变化时,另外一个引用数据类型对象的内容同样发生改变,一变俱变,符合浅复制的特点。
2、浅复制原因分析
在BeanUtils工具类中,BeanUtilsBean类对于Bean对象的操作方式是通过获得Bean对象的存取器方法进而操作对象,但Bean对象里属性的数据类型不唯一,因此BeanUtils工具类还提供了多种类型的转换器以匹配不同Bean里不同的数据类型。但当找不到引用数据类型对象的转换器时,此时BeanUtilsBean对于该引用数据类型的操作方式是直接返回该引用数据类型。也即返回的引用数据类型的值和用户Bean对象里引用数据类型的值一致,因此返回的引用数据类型的内存地址和用户传入的引用数据类型对象的内存地址空间一致,故最终才会出现符合浅复制特点的测试结果。
对源码进行Debug也能发现,当找不到合适的converter时,BeanUtils组件采取的方式是直接return返回当前类对象,所以内存地址空间并没有发生任何变化,故才会导致浅复制。
3、修改源码实现深复制
需要强调一点,如果直接下载官网的jar包是无法获得修改源码权限的,下载source目录下的tar包解压后就可以修改底层代码了。
在前面已经分析出组件浅复制的根本原因是当找不到合适的转换器时,返回的数据前后的内存地址空间一致,因此可以通过反射对返回的目标数据对象内存地址空间进行干预,通俗的话讲就是通过反射在JVM开辟的内存空间里使用反射构建一个新的对象即可。
//修改后的convert方法
protected Object convert(final Object value, final Class<?> type) {
final Converter converter = getConvertUtils().lookup(type);
if (converter != null) {
log.trace(" USING CONVERTER " + converter);
return converter.convert(type, value);
} else {
try {
Object obj = type.getConstructor().newInstance();
Field[] declaredFields = type.getDeclaredFields();
String fieldName = null;
for (Field declaredField : declaredFields) {
String name = declaredField.getName();
fieldName = name.substring(0,1).toUpperCase() + name.substring(1);
Method getMethod = type.getMethod("get"+fieldName);
Method setMethod = type.getMethod("set"+fieldName, declaredField.getType());
Object invoke = getMethod.invoke(value);
setMethod.invoke(obj, invoke);
}
if(obj != null){
return obj;
}
} catch (InstantiationException e1) {
throw new RuntimeException(e1);
} catch (IllegalAccessException e2) {
throw new RuntimeException(e2);
} catch (InvocationTargetException e3) {
throw new RuntimeException(e3);
} catch (NoSuchMethodException e4) {
throw new RuntimeException(e4);
}
return value;
}
}
4、打包测试
通过maven进行打包后替换原来的jar包并再次进行测试,运行结果如下:
可以看出==比较后的结果为false,==比较对于引用数据类型比较的是地址,结果为false显示出两个对象的内存地址空间不一致。
5、番外
Apache Commons BeanUtils组件里需要加载的组件较多,比如内置的Log等等,因此导致该组件比较笨重,市面上也有很多其他的辅助操作Bean对象的组件库,如Spring BeanUtils和BeanCopies。较之Apache的BeanUtils,Spring的较为轻量级,BeanCopies操作字节码的方式速度更快。