

为啥不建议用BeanUtils.copyProperties拷贝数据 - 京东云技术团队
source link: https://www.cnblogs.com/jingdongkeji/p/17920721.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在实际的业务开发中,我们经常会碰到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拷贝数据。文中不足之处,欢迎补充和指正。
作者:京东科技 孙扬威
来源:京东云开发者社区 转载请注明来源
Recommend
-
33
缘起 有一次开发过程中,刚好看到小伙伴在调用 set 方法,将数据库中查询出来的 Po 对象的 属性拷贝 到 Vo 对象中,类似这样:
-
46
来源: http://h5ip.cn/q846 背景 最近项目中在和第三方进行联调一个接口,我们这边发送http请求给对方,然后接收对方的回应,代码都是老代码。根据注释,对方的SDK...
-
44
点击上方 “ 匠心零度 ” ,选择“
-
39
在做业务的时候,为了隔离变化,我们会将 DAO 查询出来的 DO 和对前端提供的 DTO 隔离开来。大概 90% 的时候,它们的结构都是类似的;但是我们很不喜欢写很多冗长的 b.se...
-
38
Bean-Mapping日常开发中经常需要将一个对象的属性,赋值到另一个对象中。常见的工具有很多,但都多少不够简洁,要么不够强大。我们经常使用的SpringBeanUtils性能较好,但是特性不足。Bean-Mapping提供了很多丰富的特性,便于日常开发。如果你追求更加极致的性能,...
-
15
BeanUtils 如何拷贝 List? 一、背景 我们在 DO 、 Model 、 VO 层数据间可能经常转换数据: Entity 对应的是持久层数据结构(一般是数据库...
-
29
简介:我们到底应该选择哪种属性拷贝类工具更加合适呢?为什么阿里巴巴Java开发手册中提到禁止使用Apache BeanUtils呢? 作者 | Hollis 在日常开发中,我们经常需要给对象进行赋值,通常会调用其set/get方法,有些时...
-
5
12种 vo2dto 方法,就 BeanUtils.copyProperties 压测最拉胯!【快双11了,别用错喽】作者:小傅哥博客:
-
7
还在用BeanUtils拷贝对象?MapStruct才是王者!【附源码】 - 阿Q说代码的个人空间 - OSCHINA - 中文开源技术交流社区 前几天,远在北京的小伙伴在群里抛出了“MapStruct”的概念。对于只闻其名,未见其人的我来说,决定对其研究一番。本文我...
-
11
12种 vo2dto 方法,就 BeanUtil.copyProperties 压测数据最拉跨!持续坚持原创输出,点击蓝字关注我吧 作者:小傅哥博客:https://bugstack.cn ❝ 沉淀、分享、成长,让自己和他人都能有所收获!...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK