Spring的BeanUtil的copyProperties方法 慎用!!

背景

笔者在用工作场景中发现代码中一个bug,有两批对象,A批和B批,发现对B批没有进行什么操作,但是对象的内容变化了。

所以,就看B的对象是如何创建的,发现B的对象是A批对象通过Spring的BeanUtil的copyProperties方法进行赋值的,虽然,B是new出来后,然后进行拷贝,而不是直接使用A批对象的引用。
什么意思呢?
就是

A a = new A();
B b = new B();
//省略对a对象进行属性set操作后...
listA.add(a);
BeanUtils.copyProperties(a,b);
listB.add(b);

这个是spring提供的一个工具类

Spring的BeanUtil的copyProperties方法 慎用!!_第1张图片

验证

写了一个小的test案例

public class BeanCopyTest {


    public static void main(String[] args) {
        copy();
    }
    public static void copy(){
        Student zhangsan = new Student();
        Student lisi = new Student();

        zhangsan.setAge(18);
        zhangsan.setDesc("I love playing the game");
        zhangsan.setScore(60f);
        zhangsan.setStudentId(1);
        Map<String,String> other = new HashMap<>();
        other.put("height","180");
        other.put("weight","150");
        zhangsan.setOther(other);

        BeanUtils.copyProperties(zhangsan,lisi);

        System.out.println("before zhangsan has car:");
        System.out.println("zhangsan:");
        System.out.println(zhangsan);
        System.out.println("lisi:");
        System.out.println(lisi);
        zhangsan.setDesc("I love car!");
        zhangsan.getOther().put("hasCar","yes!");
        System.out.println("after zhangsan car");
        System.out.println("zhangsan:");
        System.out.println(zhangsan);
        System.out.println("lisi:");
        System.out.println(lisi);

        System.out.println("================");
        System.out.println("zhangsan other hashcode:"+zhangsan.getOther().hashCode());
        System.out.println("lisi other hashcode:"+lisi.getOther().hashCode());
        System.out.println(zhangsan.getOther()==lisi.getOther()?"引用相同":"引用不同");

    }

看输出结果:
Spring的BeanUtil的copyProperties方法 慎用!!_第2张图片
而lisi,现在并没有car!但是也有了!
可以知道,对于基础类型以及String类型都是值拷贝,不会发生问题,但是对于Map这样稍微复杂点的引用,则使用的是引用拷贝,也就是说,整个是一个浅拷贝!

深拷贝,浅拷贝问题可参考
https://www.cnblogs.com/plokmju/p/7357205.html

然后查看源码

1、下面这块主要是通过反射读取类的属性,字段,然后依次赋值,如果字段不是public的,还要临时设置为public
Spring的BeanUtil的copyProperties方法 慎用!!_第3张图片
2、调用invoke,执行set方法,发现只是将原来的a对象的map引用设值过去。
Spring的BeanUtil的copyProperties方法 慎用!!_第4张图片

解决办法

1、可以自行实现clone接口
2、可使用深度拷贝的第三方工具,比如dozer,或者apache的common lang3 中的工具包等
这里用dozer说明,它主要是深度映射,包括一些复杂类型的。
当然,dozer性能相比是垫底的。
可以参考如下文章

https://zhuanlan.zhihu.com/p/37942446

3、通过序列化,反序列化

public class BeanCopyTest {


    public static void main(String[] args) {
        copy();
    }
    public static void copy(){
        Student zhangsan = new Student();
//        Student lisi = new Student();

        zhangsan.setAge(18);
        zhangsan.setDesc("I love playing the game");
        zhangsan.setScore(60f);
        zhangsan.setStudentId(1);
        Map<String,String> other = new HashMap<>();
        other.put("height","180");
        other.put("weight","150");
        zhangsan.setOther(other);

//        BeanUtils.copyProperties(zhangsan,lisi);
        //copy Using dozer
        Mapper mapper = new DozerBeanMapper();
        Student lisi = mapper.map(zhangsan, Student.class);
        System.out.println("before zhangsan has car:");
        System.out.println("zhangsan:");
        System.out.println(zhangsan);
        System.out.println("lisi:");
        System.out.println(lisi);
        zhangsan.setDesc("I love car!");
        zhangsan.getOther().put("hasCar","yes!");
        System.out.println("after zhangsan car");
        System.out.println("zhangsan:");
        System.out.println(zhangsan);
        System.out.println("lisi:");
        System.out.println(lisi);

        System.out.println("================");
        System.out.println("zhangsan other hashcode:"+zhangsan.getOther().hashCode());
        System.out.println("lisi other hashcode:"+lisi.getOther().hashCode());
        System.out.println(zhangsan.getOther()==lisi.getOther()?"引用相同":"引用不同");

    }

}

输出:
Spring的BeanUtil的copyProperties方法 慎用!!_第5张图片

你可能感兴趣的:(追源码,Java,序列化)