为啥不建议用BeanUtils.copyProperties拷贝数据
在实际的业务开发中,我们经常会碰到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拷贝数据。文中不足之处,欢迎补充和指正。
作者:京东科技 孙扬威
来源:京东云开发者社区 转载请注明来源
为啥不建议用BeanUtils.copyProperties拷贝数据的更多相关文章
- BeanUtils.copyProperties()拷贝属性时,忽略空值
把source的属性值复制给target的相同属性上,注意:双方需要复制的属性要有get.set方法 BeanUtils.copyProperties(source, target, PublicUt ...
- spring: beanutils.copyproperties将一个对象的数据塞入到另一个对象中(合并对象)
spring: beanutils.copyproperties将一个对象的数据塞入到另一个对象中(合并对象) 它的出现原因: BeanUtils提供对Java反射和自省API的包装.其主要目的是利用 ...
- 吧,其实spring自带的BeanUtils就有这样的功能,引入spring-beans和spring-core之后,就有BeanUtils.copyProperties(a, b);可以实现两个javabean之间的相互拷贝,自己写的就当是研究咯---https://www.cnblogs.com/NieXiaoHui/p/7150928.html
吧,其实spring自带的BeanUtils就有这样的功能,引入spring-beans和spring-core之后,就有BeanUtils.copyProperties(a, b);可以实现两个ja ...
- spring BeanUtils.copyProperties只拷贝不为null的属性
在MVC的开发模式中经常需要将model与pojo的数据绑定,apache和spring的工具包中都有BeanUtils,使用其中的copyProperties方法可以非常方便的进行这些工作,但在实际 ...
- 007-TreeMap、Map和Bean互转、BeanUtils.copyProperties(A,B)拷贝、URL编码解码、字符串补齐,随机字母数字串
一.转换 1.1.TreeMap 有序Map 无序有序转换 使用默认构造方法: public TreeMap(Map<? extends K, ? extends V> m) 1.2.Ma ...
- 12种 vo2dto 方法,就 BeanUtils.copyProperties 压测最拉胯!【快双11了,别用错喽】
作者:小傅哥 博客:https://bugstack.cn 原文:https://mp.weixin.qq.com/s/Xq7oQg7dYESMYxHVnxX8Dw 沉淀.分享.成长,让自己和他人都能 ...
- java中 BeanUtils.copyProperties的用法
BeanUtils提供了对java发射和自省API的包装,这里对BeanUtils.copyProperties的用法做一个小小的介绍. 通过函数名可以知道,copyProperties函数是对属性进 ...
- java Beanutils.copyProperties( )用法
这是一篇开发自辩甩锅稿~~~~ 昨天测试小姐姐将我的一个bug单重开了,emmmm....内心OS:就调整下对象某个属性类型这么简单的操作,我怎么可能会出错呢,一定不是我的锅!!but再怎么抗拒,bu ...
- BeanUtils 如何拷贝 List?
BeanUtils 如何拷贝 List? 一.背景 我们在DO.Model.VO层数据间可能经常转换数据: Entity对应的是持久层数据结构(一般是数据库表的映射模型); Model 对应的是业务层 ...
- BeanUtils.copyProperties() 用法
BeanUtils提供对Java反射和自省API的包装.其主要目的是利用反射机制对JavaBean的属性进行处理.我们知道,一个JavaBean通常包含了大量的属性,很多情况下,对JavaBean的处 ...
随机推荐
- API技术的使用场景
互联网的发展和普及,API技术也变得越来越重要.API是应用程序接口,它是一种连接不同应用程序或系统之间数据交换和通信的方式.API技术不仅提高了不同应用程序之间的互操作性,还加速了应用程序的开发 ...
- QA|新版Pycharm如何关闭和开启自动补全功能?|Pycharm|工具相关
自动补全开启状态: 自动补全关闭状态: 建议:新学者建议开启自动补全,这样可以把各个函数方法记忆的更加深刻!
- Elasticsearch整合SpringBoot案例
1.elasticsearch官方文档的使用与介绍 1.1.Rest客户端初始化官方文档链接: https://www.elastic.co/guide/en/elasticsearch/client ...
- Batch Normalization 批量归一化的运算过程
Batch Normalization 批量归一化 具体的运算过程: 假设经过卷积计算过后得到的feature map的尺寸为 2*3*2*2. 其中2代表的是batch的大小,3代表的是通道chan ...
- Java开发面试--群面专区
目录 一.群面背景 二.群面流程 三.群面角色 四.群面细节 五.群面礼仪 六.群面话术 七.个人演讲 八.群面题型 群面也称无领导小组~ 候选人们被要求在一个相对自由的环境中展示他们的能力,并在没有 ...
- c# 引入同一个版本dll(比如包含opencv不同模块的dll,但是版本却是一致的)
1.使用dnSpy.exe工具,打开相关dll 在左侧"管理器"中选中并点击右键 2.在弹出菜单中点击编辑程序集 3.更改名称和版本号 4.工具栏-文件-全部保存 5.在VS中引入 ...
- Django + celery + redis 执行异步任务及查看结果
官方文档 https://docs.celeryproject.org/en/latest/django/first-steps-with-django.html#using-celery-wit ...
- 深入解析css-笔记
前言 本文章是根据<深入解析CSS>一书所作的学习笔记,书中的知识点基本都概括在这.希望对您有帮助,另外本博客是通过word笔记文档导入,虽然后续对内容和代码相关进行了一些格式处理,但还是 ...
- JVM Stack and Frame
Overview Sharing a single thread within the district: PC Register/JVM Stack/Native Method Stack.All ...
- Ubuntu 编辑文件、安装、删除软件等常用命令(持续更新)
一.编辑文件 1. sudo vi 文件名,进入文件页面,如图: 2. 按 i 键或者 o 键,进入编辑,左下角出现---------INSERT---------信息 3. 输入完毕后,按ESC退出 ...