【BeanUtils.copyProperties】BeanUtils.copyProperties必须避开的坑

前提

日常开发中,经常涉及到DO、DTO、VO对象属性拷贝赋值,很容易想到org.springframework.beans.BeanUtils的copyProperties 。它会自动通过反射机制获取源对象和目标对象的属性,并将对应的属性值进行复制。可以减少手动编写属性复制代码的工作量,提高代码的可读性和维护性。

1.类型不匹配

源对象与目标对象类型不匹配

@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
    }
}

2. BeanUtils.copyProperties是浅拷贝

  • 浅拷贝:只复制对象及其引用,而不复制引用指向的对象本身
  • 深拷贝:会创建一个新对象,其他属性跟复制对象一模一样。

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"

3.属性名称不一致

两个 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

4.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

5.注意引入的包

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

6.Boolean类型数据+is属性开头的坑

一个为基本类型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

7.查找不到字段引用

如果使用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;
       }
}

8.不同内部类,即使相同属性,也是赋值失败

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)

9.bean对应的属性,没有getter和setter方法,赋值失败

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 

10.BeanUtils.copyProperties + 泛型

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)

11.性能问题

简单的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方法

你可能感兴趣的:(#,Java,处理各种情况,java,servlet,spring)