首页 > 其他分享 >为啥不建议用BeanUtils.copyProperties拷贝数据

为啥不建议用BeanUtils.copyProperties拷贝数据

时间:2023-12-22 11:48:21浏览次数:27  
标签:String private copyProperties 拷贝 BeanUtils public

在实际的业务开发中,我们经常会碰到VO、BO、PO、DTO等对象属性之间的赋值,当属性较多的时候我们使用get,set的方式进行赋值的工作量相对较大,因此很多人会选择使用spring提供的拷贝工具BeanUtils的copyProperties方法完成对象之间属性的拷贝。通过这种方式可以很大程度上降低我们手动编写对象属性赋值代码的工作量,既然它那么方便为什么还不建议使用呢?下面是我整理的BeanUtils.copyProperties数据拷贝一些常见的

1:属性类型不一致导致拷贝失败

这个坑可以细分为如下两种:

(1)同一属性的类型不同

在实际开发中,很可能会出现同一字段在不同的类中定义的类型不一致,例如ID,可能在A类中定义的类型为Long,在B类中定义的类型为String,此时如果使用BeanUtils.copyProperties进行拷贝,就会出现拷贝失败的现象,导致对应的字段为null,对应案例如下:

public class BeanUtilsTest {

    public static void main(String[] args) {
        SourcePoJo sourcePoJo = new SourcePoJo("jingdong", (long) 35711);
        TargetPoJo targetPoJo = new TargetPoJo();
        BeanUtils.copyProperties(sourcePoJo,targetPoJo);
        System.out.println(targetPoJo);
    }
}
@Data
@AllArgsConstructor
class SourcePoJo{
    private String username;
    private Long id;
}

@Data
class TargetPoJo{
    private String username;
    private String id;
}

对应的运行结果如下:

可以看到id字段由于类型不一致,导致拷贝后的值为null。

(2)同一字段分别使用包装类型和基本类型

如果通一个字段分别使用包装类和基本类型,在没有传递实际值的时候,会出现异常,具体案例如下:

public class BeanUtilsTest {

    public static void main(String[] args) {
        SourcePoJo sourcePoJo = new SourcePoJo();
        sourcePoJo.setUsername("joy");
        TargetPoJo targetPoJo = new TargetPoJo();
        BeanUtils.copyProperties(sourcePoJo,targetPoJo);
        System.out.println(targetPoJo);
    }
}
@Data
class SourcePoJo{
    private String username;
    private Long id;
}

@Data
class TargetPoJo{
    private String username;
    private long id;
}

在测试案例中,id字段在拷贝源和拷贝目标中分别使用包装类型和基本类型,可以看到下面在拷贝时出现了异常。

注意:如果一个布尔类型的属性分别使用了基本类型和包装类型,且属性名如果使用is开头,例如isSuccess,也会导致拷贝失败。

2:null值覆盖导致数据异常

在业务开发时,我们可能会有部分字段拷贝的需求,被拷贝的数据里面如果某些字段有null值存在,但是对应的需要被拷贝过去的数据的相同字段的值并不为null,如果直接使用 BeanUtils.copyProperties 进行数据拷贝,就会出现被拷贝数据的null值覆盖拷贝目标数据的字段,导致原有的数据失效。

对应的案例如下:

public class BeanUtilsTest {

    public static void main(String[] args) {
        SourcePoJo sourcePoJo = new SourcePoJo();
        sourcePoJo.setId("35711");
        TargetPoJo targetPoJo = new TargetPoJo();
        targetPoJo.setUsername("Joy");
        BeanUtils.copyProperties(sourcePoJo,targetPoJo);
        System.out.println(targetPoJo);
    }
}
@Data
class SourcePoJo{
    private String username;
    private String id;
}

@Data
class TargetPoJo{
    private String username;
    private String id;
}

对应的运行结果如下:

可以看到拷贝目标结果中原本有值的username字段,它的值被覆盖成了null。虽然可以使用 BeanUtils.copyProperties 的重载方法,配合自定义的 ConvertUtilsBean 来实现部分字段的拷贝,但是这么做本身也比较复杂,也就失去了使用BeanUtils.copyProperties 拷贝数据的意义,因此也不推荐这么做。

3:导包错误导致拷贝数据异常

在使用 BeanUtils.copyProperties 拷贝数据时,如果项目中同时引入了Spring的beans包和Apache的beanutils包,在导包的时候,如果导入错误,很可能导致数据拷贝失败,排查起来也不太好发现。我们通常使用的是Sping包中的拷贝方法,两者的区别如下:

//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

4:查找不到字段引用,修改内容难以溯源

在开发或者排查问题过程中,如果我们在链路中查找某个字段值(调用方并未传递)的来源,我们可能会通过全文搜索的方式,去找它对应的赋值方法(例如set方式、build方式等),但是如果在链路中使用BeanUtils.copyProperties拷贝了数据,就很难快速定位到赋值的地方,导致排查效率较低。

5:内部类数据无法成功拷贝

内部类数据无法正常拷贝,及时类型和字段名均相同也无法拷贝成功,如下所示:

public class BeanUtilsTest {

    public static void main(String[] args) {
        SourcePoJo sourcePoJo = new SourcePoJo();
        sourcePoJo.setUsername("joy");
        SourcePoJo.InnerClass innerClass = new SourcePoJo.InnerClass("sourceInner");
        sourcePoJo.innerClass=innerClass;
        System.out.println(sourcePoJo.toString());
        TargetPoJo targetPoJo = new TargetPoJo();
        BeanUtils.copyProperties(sourcePoJo,targetPoJo);
        System.out.println(targetPoJo.toString());
    }
}
//下面是类的信息,这里就直接放到一块展示了
@Data
@ToString
public class SourcePoJo{
    private String username;
    private Long id;
    public InnerClass innerClass;
    @Data
    @ToString
    @AllArgsConstructor
    public static class InnerClass{
        public String innerName;
    }
}

@Data
@ToString
public class TargetPoJo{
    private String username;
    private Long id;
    public InnerClass innerClass;
    @Data
    @ToString
    public static class InnerClass{
        public String innerName;
    }
}

下面是运行结果:

上面案例中,在拷贝源和拷贝目标中各自存在一个内部类InnerClass,虽然这个内部类属性也相同,类名也相同,但是在不同的类中,因此Spring会认为属性不同,因此不会拷贝数据。

6:BeanUtils.copyProperties是浅拷贝

这里我先给大家复习一下深拷贝和浅拷贝。

浅拷贝是指创建一个新对象,该对象的属性值与原始对象相同,但对于引用类型的属性,仍然共享相同的引用。也就是说在浅拷贝下,当原始内容的引用属性值发生变化时,被拷贝对象的引用属性值也会随之发生变化。

深拷贝是指创建一个新对象,该对象的属性值与原始对象相同,包括引用类型的属性。深拷贝会递归复制引用对象,创建全新的对象,所以深拷贝拷贝后的对象与原始对象完全独立。

下面是对应的代码示例:

public class BeanUtilsTest {

    public static void main(String[] args) {
        Person sourcePerson = new Person("sunyangwei",new Card("123456"));
        Person targetPerson = new Person();
        BeanUtils.copyProperties(sourcePerson, targetPerson);
        sourcePerson.getCard().setNum("35711");
        System.out.println(targetPerson);
    }
}


@Data
@AllArgsConstructor
class Card {
    private String num;
}

@NoArgsConstructor
@AllArgsConstructor
@Data
class Person {
    private String name;
    private Card card;
}

下面是运行结果:

总结:通过代码运行结果我们可以发现,一旦你在拷贝后修改了原始对象的引用类型的数据,就会导致拷贝数据的值发生异常,这种问题排查起来也比较困难。

7:底层实现为反射拷贝效率低

BeanUtils.copyProperties底层是通过反射获取到对象的set和get方法,然后通过get、set完成数据的拷贝,整体拷贝效率较低。

下面是使用BeanUtils.copyProperties拷贝数据和直接set的方式赋值效率对比,为了便于直观的看出效果,这里以拷贝1万次为例:

public class BeanUtilsTest {

    public static void main(String[] args) {
        long copyStartTime = System.currentTimeMillis();
        User sourceUser = new User("sunyangwei");
        User targetUser = new User();
        for(int i = 0; i < 10000; i++) {
            BeanUtils.copyProperties(sourceUser, targetUser);
        }
        System.out.println("copy方式:"+(System.currentTimeMillis()-copyStartTime));

        long setStartTime = System.currentTimeMillis();
        for(int i = 0; i < 10000; i++) {
            targetUser.setUserName(sourceUser.getUserName());
        }
        System.out.println("set方式:"+(System.currentTimeMillis()-setStartTime));
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class User{
    private String userName;
}

下面是执行的效率结果对比:

可以发现,常规的set和BeanUtils.copyProperties对比,性能差距非常大。因此,慎用BeanUtils.copyProperties。

以上就是在使用BeanUtils.copyProperties拷贝数据时常见的坑,这些坑大多都是比较隐蔽的,出了问题不太好排查,因此不建议在业务中使用BeanUtils.copyProperties拷贝数据。文中不足之处,欢迎补充和指正。

作者:京东科技 孙扬威

来源:京东云开发者社区 转载请注明来源

标签:String,private,copyProperties,拷贝,BeanUtils,public
From: https://www.cnblogs.com/jingdongkeji/p/17920721.html

相关文章

  • 为啥不建议用BeanUtils.copyProperties拷贝数据 | 京东云技术团队
    在实际的业务开发中,我们经常会碰到VO、BO、PO、DTO等对象属性之间的赋值,当属性较多的时候我们使用get,set的方式进行赋值的工作量相对较大,因此很多人会选择使用spring提供的拷贝工具BeanUtils的copyProperties方法完成对象之间属性的拷贝。通过这种方式可以很大程度上降低我们手动编......
  • 属性拷贝忽略某些字段
    在更新数据时,如果使用的是UUID随机生成id,在属性拷贝后id就会和数据库中的该条数据的id不一致,导致更新失败并且新增一条除了id字段其他字段都相同的一条数据,这时就可以使用忽略某些字段的方法进行属性拷贝,比如在拷贝时忽略id,这样就只是将除了id之外的其他所有字段进行拷贝从而......
  • Java序列化和反序列化 Serializable BeanUtils.copyProperties赋值属性方法
    Java序列化和反序列化SerializableBeanUtils.copyProperties赋值属性方法packagecom.example.core.mydemo.java;importcom.example.core.mydemo.json2.GsonUtils;importorg.springframework.beans.BeanUtils;importjava.io.*;/***Java序列化和反序列化Serializ......
  • java深拷贝的几种方法
    使用序列化和反序列化:将集合对象序列化为字节流,然后再反序列化成一个新的对象。这种方式需要确保集合中的所有元素都是可序列化的。importjava.io.*;publicstaticsource){try{ByteArrayOutputStreambyteOut=newByteArrayOutputStream();ObjectOutputStreamout=new......
  • C++(深拷贝、潜拷贝)
    在C++中,深拷贝(deepcopy)和浅拷贝(shallowcopy)是涉及对象复制的两个概念,特别是当涉及到类或结构体时。下面我将解释这两个概念:1.浅拷贝(ShallowCopy):浅拷贝是指将一个对象的内容复制到另一个对象,但如果对象中包含指针,则只会复制指针的值,而不是指针所指向的数据。这意味着两个对象......
  • 深浅拷贝的区别和实现
    数组的浅拷贝:如果是数组,我们可以利用数组的一些方法,比如slice,concat方法返回一个新数组的特性来实现拷贝,但假如数组嵌套了对象或者数组的话,使用concat方法克隆并不完整,如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或数组,就会只拷贝对象和数组的引用,这样我们无论......
  • vue2子组件拷贝父组件传递的参数
    在Vue2中,父组件向子组件传递参数时,如果参数是对象或数组(即非基本数据类型),那么子组件可以直接修改这个参数,这会影响到父组件的数据。如果你想避免这种情况,你可以让子组件对父组件的传参进行深度拷贝。这样,子组件就可以自由修改拷贝后的参数,而不会影响到父组件的数据。这是一个例......
  • js实现深拷贝复制的方法
    utils/deepCopy.jsexportdefaultfunctiondeepCopy(obj){//深拷贝方法if(typeofobj!=='object'||obj===null){ returnobj;}letcopy;if(objinstanceofArray){ copy=[]; for(leti=0;i<obj.length;i++){ copy[i]=d......
  • 关于深拷贝与浅拷贝的区别
    简而言之:深浅拷贝的区别关键在于拷贝的对象类型是否可变。我们可以总结出以下三条规则:对于可变对象来说,深拷贝和浅拷贝都会开辟新地址,完成对象的拷贝而对于不可变对象来说,深浅拷贝都不会开辟新地址,只是建立引用关联,等价于赋值对于复合对象来说,浅拷贝只考虑最外层的类型,复合......
  • 深浅拷贝
    深浅拷贝【1】浅拷贝浅拷贝仅复制对象的一层内容,如果对象中包含了可变数据类型(如列表、字典等),则拷贝的对象将与原对象共享这些可变数据类型的引用。这意味着对于原对象中的可变数据类型进行修改操作时,拷贝的对象也会受到影响。importcopy#原对象list=[1,2,[3,4]]......