Su的技术博客

  • 首页
  • 原创
  • 视频
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
    • AI工具集
    • 工具清单
    • JSON在线格式化
    • JSON在线比较
    • SQL在线格式化
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. Java
  3. 正文
                           

【京东】为啥不建议用BeanUtils.copyProperties拷贝数据

2024-02-18 1640点热度 0人点赞 0条评论
在实际的业务开发中,我们经常会碰到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;
}

 

对应的运行结果如下:
为啥不建议用BeanUtils.copyProperties拷贝数据
可以看到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字段在拷贝源和拷贝目标中分别使用包装类型和基本类型,可以看到下面在拷贝时出现了异常。
为啥不建议用BeanUtils.copyProperties拷贝数据
注意:如果一个布尔类型的属性分别使用了基本类型和包装类型,且属性名如果使用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;
}
对应的运行结果如下:
为啥不建议用BeanUtils.copyProperties拷贝数据
可以看到拷贝目标结果中原本有值的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;
    }
}
下面是运行结果:
为啥不建议用BeanUtils.copyProperties拷贝数据
上面案例中,在拷贝源和拷贝目标中各自存在一个内部类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;
}
下面是运行结果:
为啥不建议用BeanUtils.copyProperties拷贝数据
总结:通过代码运行结果我们可以发现,一旦你在拷贝后修改了原始对象的引用类型的数据,就会导致拷贝数据的值发生异常,这种问题排查起来也比较困难。

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;
}

 

下面是执行的效率结果对比:
为啥不建议用BeanUtils.copyProperties拷贝数据
可以发现,常规的set和BeanUtils.copyProperties对比,性能差距非常大。因此,慎用BeanUtils.copyProperties。
以上就是在使用BeanUtils.copyProperties拷贝数据时常见的坑,这些坑大多都是比较隐蔽的,出了问题不太好排查,因此不建议在业务中使用BeanUtils.copyProperties拷贝数据。文中不足之处,欢迎补充和指正。
-end-

本文仅供学习!所有权归属原作者。侵删!文章来源: 京东云开发者 -孙扬威 :http://mp.weixin.qq.com/s/bzLviYljo0RpDZnjbs5Ftw

更多文章:

  1. 殷浩详解DDD 第四讲:领域层设计规范
  2. 设计模式在外卖营销业务中的实践
  3. 手把手教你实战TDD
  4. 【进阶玩法】策略+责任链+组合实现合同签章
  5. 殷浩详解DDD系列 第一讲 - Domain Primitive
  6. 从代码到设计的性能优化指南
  7. 殷浩详解DDD 第三讲 - Repository模式
  8. 殷浩详解DDD系列 第二讲 - 应用架构
  9. 浅析设计模式1 —— 工厂模式
  10. Spring事务无法生效的11个场景
标签: 京东 Java 开发 BeanUtils 经验分享
最后更新:2024-02-17

coder

分享干货文章,学习先进经验。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

广告
文章目录
  • 1:属性类型不一致导致拷贝失败
  • 2:null值覆盖导致数据异常
  • 3:导包错误导致拷贝数据异常
  • 4:查找不到字段引用,修改内容难以溯源
  • 5:内部类数据无法成功拷贝
  • 6:BeanUtils.copyProperties是浅拷贝
  • 7:底层实现为反射拷贝效率低
最新 热点 推荐
最新 热点 推荐
Anthropic Code with Claude 开发者大会:开启 AI Agent 新时代 视频笔记-微服务架构P4:必懂5种设计模式 视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构 干货 | 论Elasticsearch数据建模的重要性 马蜂窝消息总线——面向业务的消息服务设计 基于 MySQL Binlog 实现可配置的异构数据同步 视频笔记:Google发布Agent2Agent协议 视频笔记:什么是微服务,为什么是微服务?
基于 MySQL Binlog 实现可配置的异构数据同步马蜂窝消息总线——面向业务的消息服务设计视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构干货 | 论Elasticsearch数据建模的重要性视频笔记-微服务架构P4:必懂5种设计模式Anthropic Code with Claude 开发者大会:开启 AI Agent 新时代
系统设计 | “胖瘦” BFF:常见的两种微服务形态 5.单体架构(译) Spring Boot 与 Quarkus 对比解析:谁才是你的最佳Java框架? 系统设计-业务表5要素 微服务架构VS单体架构,为什么要选择微服务 用图讲解SOLID设计原则 系统设计 | 术语管理初探讨 订单超时怎么处理?我们用这种方案

CRUD (1) Event Sourcing (1) graphql (1) id (1) NoSQL (1) quarkus (1) rest (1) RocketMQ (2) Spring Boot (1) zk (1) zookeeper (1) 上下文 (1) 事务消息 (1) 二级缓存 (1) 值对象 (1) 关系数据库 (1) 分布式缓存 (1) 原子性 (1) 唯一ID (1) 商品 (1) 多对多 (1) 子域 (1) 字符集 (1) 客户端心跳 (1) 幂等 (2) 干货 (1) 并发 (1) 应用场景 (1) 应用架构图 (1) 康威定律 (2) 异步复制 (1) 微服务架构 (3) 总体方案 (1) 技术方案 (2) 技术架构 (2) 技术架构图 (1) 技能 (1) 持续集成 (1) 支撑域 (1) 故障恢复 (1) 数据架构图 (1) 方案选型 (1) 日记 (1) 服务发现 (1) 服务治理 (1) 服务注册 (2) 机房 (1) 核心域 (1) 泄漏 (1) 洋葱架构 (1) 消息队列 (5) 源码剖析 (1) 灰度发布 (1) 熔断 (1) 生态 (1) 画图工具 (1) 研发团队 (1) 线程 (2) 组织架构 (1) 缓存架构 (1) 编码 (1) 视频 (20) 读写分离 (1) 贵州 (1) 软件设计 (1) 迁移 (1) 通用域 (1) 集群化 (1) 雪花算法 (1) 顺序消息 (1)

推荐链接🔗
  • AI工具集
  • 工具箱🛠️

COPYRIGHT © 2014-2025 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2