为啥不建议用BeanUtils.copyProperties拷贝数据 | 京东云技术团队

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

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

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

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

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

1public class BeanUtilsTest { 2 3 public static void main(String[] args) { 4 SourcePoJo sourcePoJo = new SourcePoJo("jingdong", (long) 35711); 5 TargetPoJo targetPoJo = new TargetPoJo(); 6 BeanUtils.copyProperties(sourcePoJo,targetPoJo); 7 System.out.println(targetPoJo); 8 } 9} 10@Data 11@AllArgsConstructor 12class SourcePoJo{ 13 private String username; 14 private Long id; 15} 16 17@Data 18class TargetPoJo{ 19 private String username; 20 private String id; 21}

对应的运行结果如下:

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

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

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

1public class BeanUtilsTest { 2 3 public static void main(String[] args) { 4 SourcePoJo sourcePoJo = new SourcePoJo(); 5 sourcePoJo.setUsername("joy"); 6 TargetPoJo targetPoJo = new TargetPoJo(); 7 BeanUtils.copyProperties(sourcePoJo,targetPoJo); 8 System.out.println(targetPoJo); 9 } 10} 11@Data 12class SourcePoJo{ 13 private String username; 14 private Long id; 15} 16 17@Data 18class TargetPoJo{ 19 private String username; 20 private long id; 21}

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

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

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

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

对应的案例如下:

1public class BeanUtilsTest { 2 3 public static void main(String[] args) { 4 SourcePoJo sourcePoJo = new SourcePoJo(); 5 sourcePoJo.setId("35711"); 6 TargetPoJo targetPoJo = new TargetPoJo(); 7 targetPoJo.setUsername("Joy"); 8 BeanUtils.copyProperties(sourcePoJo,targetPoJo); 9 System.out.println(targetPoJo); 10 } 11} 12@Data 13class SourcePoJo{ 14 private String username; 15 private String id; 16} 17 18@Data 19class TargetPoJo{ 20 private String username; 21 private String id; 22}

对应的运行结果如下:

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

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

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

1//org.springframework.beans.BeanUtils(源对象在左边,目标对象在右边) 2public static void copyProperties(Object source, Object target) throws BeansException 3//org.apache.commons.beanutils.BeanUtils(源对象在右边,目标对象在左边) 4public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException

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

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

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

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

1public class BeanUtilsTest { 2 3 public static void main(String[] args) { 4 SourcePoJo sourcePoJo = new SourcePoJo(); 5 sourcePoJo.setUsername("joy"); 6 SourcePoJo.InnerClass innerClass = new SourcePoJo.InnerClass("sourceInner"); 7 sourcePoJo.innerClass=innerClass; 8 System.out.println(sourcePoJo.toString()); 9 TargetPoJo targetPoJo = new TargetPoJo(); 10 BeanUtils.copyProperties(sourcePoJo,targetPoJo); 11 System.out.println(targetPoJo.toString()); 12 } 13} 14//下面是类的信息,这里就直接放到一块展示了 15@Data 16@ToString 17public class SourcePoJo{ 18 private String username; 19 private Long id; 20 public InnerClass innerClass; 21 @Data 22 @ToString 23 @AllArgsConstructor 24 public static class InnerClass{ 25 public String innerName; 26 } 27} 28 29@Data 30@ToString 31public class TargetPoJo{ 32 private String username; 33 private Long id; 34 public InnerClass innerClass; 35 @Data 36 @ToString 37 public static class InnerClass{ 38 public String innerName; 39 } 40}

下面是运行结果:

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

6:BeanUtils.copyProperties是浅拷贝

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

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

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

下面是对应的代码示例:

1public class BeanUtilsTest { 2 3 public static void main(String[] args) { 4 Person sourcePerson = new Person("sunyangwei",new Card("123456")); 5 Person targetPerson = new Person(); 6 BeanUtils.copyProperties(sourcePerson, targetPerson); 7 sourcePerson.getCard().setNum("35711"); 8 System.out.println(targetPerson); 9 } 10} 11 12 13@Data 14@AllArgsConstructor 15class Card { 16 private String num; 17} 18 19@NoArgsConstructor 20@AllArgsConstructor 21@Data 22class Person { 23 private String name; 24 private Card card; 25}

下面是运行结果:

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

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

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

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

1public class BeanUtilsTest { 2 3 public static void main(String[] args) { 4 long copyStartTime = System.currentTimeMillis(); 5 User sourceUser = new User("sunyangwei"); 6 User targetUser = new User(); 7 for(int i = 0; i < 10000; i++) { 8 BeanUtils.copyProperties(sourceUser, targetUser); 9 } 10 System.out.println("copy方式:"+(System.currentTimeMillis()-copyStartTime)); 11 12 long setStartTime = System.currentTimeMillis(); 13 for(int i = 0; i < 10000; i++) { 14 targetUser.setUserName(sourceUser.getUserName()); 15 } 16 System.out.println("set方式:"+(System.currentTimeMillis()-setStartTime)); 17 } 18} 19 20@Data 21@AllArgsConstructor 22@NoArgsConstructor 23class User{ 24 private String userName; 25}

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

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

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

作者:京东科技 孙扬威

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

点赞
收藏

评论区

加载中...

相关推荐

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

Simple Object Copy一款idea插件,优雅转化DTO、VO、BO、PO、DO

1、什么是DTO、VO、BO、PO、DO、POJOPOJO的定义是无规则简单的对象,在日常的代码分层中pojo会被分为VO、BO、PO、DTOVO(viewobject/valueobject)表示层对象1、前端展示的数据,在接口数据返回给前端的时候需要转成VO2、个人理解使用场景,接口层服务中,将DTO转成VO,返回给前台B0(bussines

java 复制Map对象(深拷贝与浅拷贝)

java复制Map对象(深拷贝与浅拷贝)CreationTime2018年6月4日10点00分Author:Marydon1.深拷贝与浅拷贝  浅拷贝:只复制对象的引用,两个引用仍然指向同一个对象

也谈JavaScript浅拷贝和深拷贝

网上关于这个话题,讨论有很多了,根据各路情况我自己整理了一下,最后还是能接近完美的实现深拷贝,欢迎大家讨论。javascript中的对象是引用类型,在复制对象的时候就要考虑是用浅拷贝还是用深拷贝。直接赋值对象是引用类型,如果直接赋值给另外一个对象,那么只是赋值一个引用,实际上两个变量指向的同一个数据对象,如果其中一个对象的属性变更,那么另外一个也会变更。示

Java对象拷贝原理剖析及最佳实践

作者:宁海翔1前言对象拷贝,是我们在开发过程中,绕不开的过程,既存在于Po、Dto、Do、Vo各个表现层数据的转换,也存在于系统交互如序列化、反序列化。Java对象拷贝分为深拷贝和浅拷贝,目前常用的属性拷贝工具,包括Apache的

为啥不建议用BeanUtils.copyProperties拷贝数据 | 京东云技术团队 - HelloWorld