关于Spring中的BeanUtils的使用的细节和由此导致的巨坑

关于Spring中的BeanUtils的使用的细节和由此导致的巨坑

前言

本文研究关于spring带的BeanUtils的坑。

BeanUtils.copyProperties 的使用注意点

结论:

  1. 字段名字要相同(完全相同包括大小写,其实应该是set-get方法名)
  2. 类型要相同(primitive type和对应包装类同)
  3. Source必须有getter,且必须public
  4. Target必须有setter,且必须public
  5. Source中的static字段则不行(例如static和static final的都不行)
  6. Target中final的字段不会被赋值(例如Target中的final和static final字段)
  7. Source或Target中,若有父类,父类中的字段是可以复制的
  8. Source中内嵌的非普通类型的字段需要注意 (避免大坑
    1)只是把Source中的引用给到Target,Target中改变会导致两边都变化
    2)List<A> 和 List<B> 转换是OK的,只是把引用交给了后者,要注意遍历的时候会发生类型转换异常

这里说的 “非普通类型” 可以理解为除了 8种原始类型(primitive type)+8中原始类型对应的包装类+String类之外的的引用类型

  1. 其实还要注意下,如果有继承的情况下会发生什么事情(其实不会有大的问题,见后面,继承的字段也把它当做当前类的字段即可得到结论)

使用注意点

假设有个Source类,和一个Target类

Source转换到Target,一般除了基础类型的字段(这里说的 “基础类型” 是指8种原始类型、对应的包装类、String类型),如果包含有其他类型的,使用要十分谨慎!!!

  • Source里的字段名和Target里的字段名,必须完全一样,这个能够转换的第一个条件
  • Source里的字段类型和Target里的要严格相同,这是第二个条件
    • Integer 类型转 Integer 是ok的
    • int -> Integer (ok,原始类型和包装类是互通的)
    • Integer -> int (ok)
    • int -> long (not ok,同是整数仅范围不同,不行,必须严格相同类型)
    • int -> Long (not ok)
    • long -> int (not ok)
    • long -> Integer (not ok)
    • Dog -> Dog (ok)
    • Animal -> Animal (ok)
    • Dog -> Animal (not ok,即使是Dog继承Animal,不行,必须严格相同的类型)
    • Animal -> Dog (not ok)
    • List<A> -> List<B> (ok,非常大的坑,极其容易在后续使用时发生ClassCastException,参考后续的附录中的代码)
  • Source里需要被拷贝的字段需要有getter,Target的字段,需要有setter,第三个条件
  • Source里需要被拷贝的字段的getter,和Target里的setter,都只能是public的(protected/private/不写即default通通不行),第四个条件
  • Source里的字段,不能是static的(含static final),第五条件
  • Target里的字段,不能是final的(含static final),第六条件

注意:

  1. BeanUtils是浅拷贝,不是深拷贝,只是把引用弄过去

例如:Source中有A类的字段field,Target中也有A类的字段field,则Source中的field和Target中的field将会是相同的对象(哈希码相同)

如果Source中有非普通字段,谨慎用BeanUtils,最好将内嵌的转换后再set到外层,不然很多坑!!!

​ 2. Source里的父类的字段,也能被拷贝

举例

注意,无特别表示都表示Source有getter,Target有getter,且都是public的

SourceTarget是否可以拷贝
字段名:age字段名:age2
字段类型:int字段类型:int
字段类型:int字段类型:Integer
字段类型:Integer字段类型:int
字段类型:int字段类型:Long
字段类型:int字段类型:long
字段类型:Long字段类型:int
字段类型:boolean字段类型:boolean
字段类型:boolean字段类型:Boolean
字段类型:Boolean字段类型:boolean
字段类型:Dog(继承Animal)字段类型:Animal
字段类型:List<A>字段类型:List<B>是。虽然有泛型,但能赋值过去,但存在严重隐患,见附录
字段类型:Boolean字段类型:Boolean
Source继承Super1,Super1里有name字段Target继承Super2,Super2里也有name字段
Source继承Super1,Super1里有name字段Target里有name字段(不继承)
Source有name字段(不继承)Target继承Super2,Super2里有name字段
字段修饰:static,如static Integer age任意修饰符:如static/static final/普通类型/final否。(不抛异常,但读取不了Source的static字段)
字段修饰:普通字段任意修饰符:如static/普通类型(除final/static final)是(但不能set到Target是final的)
字段修饰:final任意修饰符:如static/普通类型(除final/static final)是(但不能set到Target是final的)
  1. Source中的serialVersionUID(就是Serializable要求的那个字段),会出现什么情况? 会覆盖Target中的吗? (好像不会吧,这个final的)(不会转过去,因为是static的

附录

下面演示了一个巨大的坑

public class Test_A_Fucking_Problem {
    public static void main(String[] args) {
        Source source = new Source();
        Pet p1 = new Pet();
        p1.setName("dog");

        Pet p2 = new Pet();
        p2.setName("cat");

        List<Pet> petList = new ArrayList<>();
        petList.add(p1);
        petList.add(p2);
        source.setPetList(petList);

        Target target = new Target();
        BeanUtils.copyProperties(source, target);
        System.out.println("target:" + target + ",target.petList.hashCode:"
                + (target.getPetList() == null ? "未被赋值": target.getPetList().hashCode()) + ",petList.hashCode:" + petList.hashCode());

        // ★★需要非常注意的点,Source是List<A> 但是Target是List<B>,BeanUtils是可以把值赋值到target的,因为都是List类型,它不会管泛型不同的
        // 但是,赋值过去,实际上是把对象的引用引过去,也就是 `List<B> petList` 字段其实是被赋值了 `List<A> petList` 的实例,在下面运行的过程就出现类转换异常
        // get(0) 还不会出现异常,但是get(0)后再getName,则JVM需要弄清楚这个get(0)得到是什么类型,当发现类型跟声明的泛型类型不一样的时候,就抛出了运行时异常(ClassCast...)
        System.out.println(target.getPetList().get(0));
        System.out.println(target.getPetList().get(0).getName());

    }
}
public class Source {
    private List<Pet> petList;

    public List<Pet> getPetList() {
        return petList;
    }

    public void setPetList(List<Pet> petList) {
        this.petList = petList;
    }
}

public class Target {
    private List<Pet2> petList;

    public List<Pet2> getPetList() {
        return petList;
    }

    public void setPetList(List<Pet2> petList) {
        this.petList = petList;
    }
}

public class Pet {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class Pet2 {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring FrameworkBeanUtils类提供了一个copyProperties方法,用于将一个JavaBean的属性值复制到另一个JavaBean使用该方法,您可以将一个对象的属性值复制到另一个对象,而无需手动编写代码来设置属性。 以下是copyProperties方法的使用示例: ``` // 定义源对象和目标对象 Person source = new Person(); source.setName("Tom"); source.setAge(20); Person target = new Person(); // 将源对象的属性值复制到目标对象 BeanUtils.copyProperties(source, target); // 打印目标对象的属性值 System.out.println(target.getName()); // 输出 Tom System.out.println(target.getAge()); // 输出 20 ``` 注意,copyProperties方法使用Java反射机制来获取和设置对象属性。因此,源对象和目标对象的属性名称和类型必须相同。如果源对象存在某些属性,在目标对象不存在,则这些属性将被忽略。如果目标对象存在某些属性,在源对象不存在,则这些属性将保持不变。 此外,copyProperties方法还支持将一个Map对象的属性值复制到一个JavaBean。您可以将Map对象的键作为JavaBean的属性名称,将值作为属性值传递给copyProperties方法。例如: ``` // 定义源Map和目标对象 Map<String, Object> sourceMap = new HashMap<>(); sourceMap.put("name", "Tom"); sourceMap.put("age", 20); Person target = new Person(); // 将源Map的属性值复制到目标对象 BeanUtils.copyProperties(sourceMap, target); // 打印目标对象的属性值 System.out.println(target.getName()); // 输出 Tom System.out.println(target.getAge()); // 输出 20 ``` 在这个例子,我们使用一个Map对象作为源对象,将其属性值复制到一个Person对象。注意,源Map的键必须与目标对象的属性名称相匹配。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值