日常开发中,经常涉及到DO、DTO、VO对象属性拷贝赋值,很容易想到org.springframework.beans.BeanUtils的copyProperties 。它会自动通过反射机制获取源对象和目标对象的属性,并将对应的属性值进行复制。可以减少手动编写属性复制代码的工作量,提高代码的可读性和维护性。
源对象与目标对象类型不匹配
@Data
public class SourceBean {
private Long age;
}
@Data
public class TargetBean {
private String age;
}
public class Test {
public static void main(String[] args) {
SourceBean source = new SourceBean();
source.setAge(25L);
TargetBean target = new TargetBean();
BeanUtils.copyProperties(source, target);
System.out.println(target.getAge()); //拷贝赋值失败,输出null
}
}
sourcePerson的address属性修改会影响到targetPerson的addreess值
public class Address {
private String city;
//getter 和 setter 方法省略
}
public class Person {
private String name;
private Address address;
//getter 和 setter 方法省略
}
Person sourcePerson = new Person();
sourcePerson.setName("John");
Address address = new Address();
address.setCity("New York");
sourcePerson.setAddress(address);
Person targetPerson = new Person();
BeanUtils.copyProperties(sourcePerson, targetPerson);
sourcePerson.getAddress().setCity("London");
System.out.println(targetPerson.getAddress().getCity()); // 输出为 "London"
两个 username,一个N是大写,一个n是小写
public class SourceBean {
private String username;
// getter 和 setter 方法省略
}
public class TargetBean {
private String userName;
// getter 和 setter 方法省略
}
SourceBean source = new SourceBean();
source.setUsername("捡田螺的小男孩");
TargetBean target = new TargetBean();
BeanUtils.copyProperties(source, target);
System.out.println(target.getUserName()); // 输出为 null
@Data
public class SourceBean {
private String name;
private String address;
}
@Data
public class TargetBean {
private String name;
private String address;
}
SourceBean source = new SourceBean();
source.setName("John");
source.setAddress(null);
TargetBean target = new TargetBean();
target.setAddress("田螺address");
BeanUtils.copyProperties(source, target);
System.out.println(target.getAddress()); // 输出为 null
BeanUtils.copyProperties其实有两个包,分别是spring、apache。大家注意一下哈,这两个包,是有点不一样的:注意一下哈,注意自己引入的哪个BeanUtils,写对应参数位置
//org.springframework.beans.BeanUtils(源对象在左边,目标对象在右边)
public static void copyProperties(Object source, Object target) throws BeansException
//org.apache.commons.beanutils.BeanUtils(源对象在右边,目标对象在左边)
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException
一个为基本类型boolean,一个为包装类型Boolean
当属性类型为boolean时,属性名以is开头,属性名会去掉前面的is,因此源对象和目标对象属性对不上啦。
@Data
public class SourceBean {
private boolean isTianLuo;
}
@Data
public class TargetBean {
private Boolean isTianLuo;
}
SourceBean source = new SourceBean();
source.setTianLuo(true);
TargetBean target = new TargetBean();
BeanUtils.copyProperties(source, target);
System.out.println(target.getIsTianLuo()); // 输出为 null
如果使用BeanUtils.copyProperties,就不知道是否引用到对应的ste方法啦,即查找不到字段引用。
@Data
public class SourceBean {
private boolean tianLuo;
public boolean isTianLuo(){
return tianLuo;
}
public void setTianLuo(boolean tianLuo){
this.tianLuo = tianLuo;
}
}
@Data
public class TargetBean {
private Boolean isTianLuo;
public Boolean isTianLuo(){
return tianLuo;
}
public void setTianLuo(Boolean tianLuo){
this.tianLuo = tianLuo;
}
}
CopySource和CopyTarget各自存在一个内部类InnerClass,虽然这个内部类属性也相同,类名也相同,但是在不同的类中,因此Spring会认为属性不同,不会Copy
@Data
public class CopySource {
public String outerName;
public CopySource.InnerClass innerClass;
@Data
public static class InnerClass {
public String InnerName;
}
}
@Data
public class CopyTarget {
public String outerName;
public CopyTarget.InnerClass innerClass;
@Data
public static class InnerClass {
public String InnerName;
}
}
CopySource test1 = new CopySource();
test1.outerName = "outTianluo";
CopySource.InnerClass innerClass = new CopySource.InnerClass();
innerClass.InnerName = "innerTianLuo";
test1.innerClass = innerClass;
System.out.println(test1);
CopyTarget test2 = new CopyTarget();
BeanUtils.copyProperties(test1, test2);
System.out.println(test2); //输出CopyTarget(outerName=outTianluo, innerClass=null)
BeanUtils.copyProperties要拷贝属性值成功,需要对应的bean要有getter和setter方法。因为它是用反射拿到set和get方法再去拿属性值和设置属性值的。
@Data
public class SourceBean {
private String value;
}
@Getter //没有对应的setter方法
public class TargetBean {
private String value;
}
SourceBean source = new SourceBean();
source.setValue("捡田螺的小男孩");
TargetBean target = new TargetBean();
BeanUtils.copyProperties(source, target);
System.out.println(target.getValue()); //输出null
BeanUtils.copyProperties方法拷贝包含泛型属性的对象clazz。CopyTarget和CopySource的泛型属性类型不匹配,因此拷贝赋值失败
@Data
public class CopySource {
public String outerName;
public List<CopySource.InnerClass> clazz;
@Data
public static class InnerClass {
public String InnerName;
}
}
@ToString
@Data
public class CopyTarget {
public String outerName;
public List<CopyTarget.InnerClass> clazz;
@Data
public static class InnerClass {
public String InnerName;
}
}
CopySource test1 = new CopySource();
test1.outerName = "outTianluo";
CopySource.InnerClass innerClass = new CopySource.InnerClass();
innerClass.InnerName = "innerTianLuo";
List<CopySource.InnerClass> clazz = new ArrayList<>();
clazz.add(innerClass);
test1.setClazz(clazz);
System.out.println(test1);
CopyTarget test2 = new CopyTarget();
BeanUtils.copyProperties(test1, test2);
System.out.println(test2); //输出CopyTarget(outerName=outTianluo, clazz=null)
简单的setter和BeanUtils.copyProperties对比,性能差距非常大。因此,慎用BeanUtils.copyProperties!!!
SourceBean sourceBean = new SourceBean();
sourceBean.setName("tianLuoBoy");
TargetBean target = new TargetBean();
long beginTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) { //循环10万次
target.setName(sourceBean.getName());
}
System.out.println("common setter time:" + (System.currentTimeMillis() - beginTime));
long beginTime1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) { //循环10万次
BeanUtils.copyProperties(sourceBean, target);
}
System.out.println("bean copy time:" + (System.currentTimeMillis() - beginTime1));
//输出
common setter time:3
bean copy time:331
要避免性能问题:可以使用原始的setter和getter方法