今天开发的时候在调用BeanUtils.copyProperties()的时候出现了异常,在网上找了一些这个方法的介绍,发现与我测试的结果不同,便记录一下我的测试过程与结果。
一.简介
首先简单介绍一下我用到这个方法的场景,有一个JavaBean的属性大部分都在另一个JavaBean中存在,现在需要把这个JavaBean中的同名的属性的值赋给另一个JavaBean对象,如果用get/set处理,需要大量的代码,用这个工具类的方法就能起到简化代码的作用。
这个方法有三个public修饰的重载方法,但其实都是调用的同一个方法copyProperties(Object source, Object target, Class editable, String[] ignoreProperties)
public static void copyProperties(Object source, Object target)
throws BeansException
{
copyProperties(source, target, null, null);
}
public static void copyProperties(Object source, Object target, Class editable)
throws BeansException
{
copyProperties(source, target, null, null);
}
public static void copyProperties(Object source, Object target, String[] ignoreProperties)
throws BeansException
{
copyProperties(source, target, null, null);
}
二.原理
下面是此方法的源码
public static void copyProperties(Object source, Object target, Class editable, 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 ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
for (int i = 0; i < targetPds.length; i++) {
PropertyDescriptor targetPd = targetPds[i];
if ((targetPd.getWriteMethod() != null) && ((ignoreProperties == null) || (!ignoreList.contains(targetPd.getName()))))
{
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if ((sourcePd != null) && (sourcePd.getReadMethod() != null)) {
try {
Method readMethod = sourcePd.getReadMethod();
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source, new Object[0]);
Method writeMethod = targetPd.getWriteMethod();
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, new Object[] { value });
}
catch (Throwable ex) {
throw new FatalBeanException("Could not copy properties from source to target", ex);
}
}
}
}
}
注意这两处代码
Object value = readMethod.invoke(source, new Object[0]);
writeMethod.invoke(target, new Object[] { value });
从源码可以看到此方法是利用反射机制对JavaBean的属性进行处理,先调用source对象的getter方法,取出各个属性的值并赋给Object对象value,再利用反射调用target对象的setter方法将value对象的值赋给相同的属性。
三.需要注意的地方
- 如果source对象的某个对象的属性值为null,而target对象的对应属性为基本类型,就会抛IllegalArgumentException异常,这点容易理解,给基本数据类型赋null值,所以一般在JavaBean中使用包装类型。
Exception in thread "main" org.springframework.beans.FatalBeanException: Could not copy properties from source to target; nested exception is java.lang.IllegalArgumentException
at org.springframework.beans.BeanUtils.copyProperties(BeanUtils.java:591)
at org.springframework.beans.BeanUtils.copyProperties(BeanUtils.java:500)
at com.haiyi.action.ElectronNote.Test.main(Test.java:21)
Caused by: java.lang.IllegalArgumentException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.beans.BeanUtils.copyProperties(BeanUtils.java:588)
... 2 more
-
从上面可以看到,该方法对属性的复制属于浅克隆,所以若是有一个list对象,赋值之后对source对象中的list进行修改,target对象中的属性也会随之修改。
-
我在网上看到有人说不支持java.util.Date,所以特地测试了一下,但是并没有报错,所以这个说法有待考究,应该不是这个方法的原因。
下面是我的测试代码(getter和setter方法就不展示了)
import java.util.Date;
import java.util.List;
public class Dog {
private Integer id;
private String name;
private String bark;
private Date birthday;
private List<String> children;
}
public class Cat {
private int id;
private String name;
private String meow;
private Date birthday;
private List<String> children;
@Override
public String toString() {
return "Cat [id=" + id + ", name=" + name + ", meow=" + meow
+ ", birthday=" + birthday + ", children=" + children + "]";
}
}
import java.util.ArrayList;
import java.util.Date;
import org.springframework.beans.BeanUtils;
public class Test {
public static void main(String[] args) {
Dog d = new Dog();
d.setId(1); //如果屏蔽掉这行代码,会报错
d.setName("旺财");
d.setBark("汪");
d.setBirthday(new Date());
ArrayList<String> children = new ArrayList<String>();
children.add("小黑");
children.add("小白");
d.setChildren(children);
Cat c = new Cat();
BeanUtils.copyProperties(d, c);
//如果需要重新拷贝一份可以使用下面代码
//c.setChildren(new ArrayList<String>(d.getChildren()));
System.out.println(c);
children.add("小花");
System.out.println(c);
}
}
下面是测试结果
Cat [id=1, name=旺财, meow=null, birthday=Wed Feb 27 10:15:29 CST 2019, children=[小黑, 小白]]
Cat [id=1, name=旺财, meow=null, birthday=Wed Feb 27 10:15:29 CST 2019, children=[小黑, 小白, 小花]]
如果不将注释的代码屏蔽,结果会是
Cat [id=1, name=旺财, meow=null, birthday=Wed Feb 27 16:19:07 CST 2019, children=[小黑, 小白]]
Cat [id=1, name=旺财, meow=null, birthday=Wed Feb 27 16:19:07 CST 2019, children=[小黑, 小白]]
看一下ArrayList 的构造方法
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
可以看到是调用了toArray()方法,这个方法是在Collection接口中定义的,来看看ArrayList是怎么实现这个方法的
/**
* Returns an array containing all of the elements in this list
* in proper sequence (from first to last element).
*
* <p>The returned array will be "safe" in that no references to it are
* maintained by this list. (In other words, this method must allocate
* a new array). The caller is thus free to modify the returned array.
*
* <p>This method acts as bridge between array-based and collection-based
* APIs.
*
* @return an array containing all of the elements in this list in
* proper sequence
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
从这个方法的描述的第二段可以看到,这个方法将返回一个新的数组,所以调用者可以随意的修改返回的数组。所以第二次测试数组中的值并没有被改变,从Arrays的源码中也能看到new了一个新的Object数组。
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
四.总结
如果JavaBean中含有大量的属性,这个方法将极大的简化我们的代码,而且只会把相同的属性的值复制,如果之前已经有值了,将覆盖之前的值。
这是我第一次写点东西,将自己平时开发中遇到的问题记录起来,希望将来能养成这个良好的习惯吧。如果发现哪里有误,可以给我留言,我及时更正。