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

1 前言

对象拷贝,是我们在开发过程中,绕不开的过程,既存在于 Po、Dto、Do、Vo 各个表现层数据的转换,也存在于系统交互如序列化、反序列化。

Java 对象拷贝分为深拷贝和浅拷贝,目前常用的属性拷贝工具,包括 Apache 的 BeanUtils、Spring 的 BeanUtils、Cglib 的 BeanCopier、mapstruct 都是浅拷贝。

1.1 深拷贝

深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容称为深拷贝。

深拷贝常见有以下四种实现方式:

  • 构造函数

  • Serializable 序列化

  • 实现 Cloneable 接口

  • JSON 序列化

1.2 浅拷贝

浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝称为浅拷贝。通过实现 Cloneabe 接口并重写 Object 类中的 clone()方法可以实现浅克隆。

2 常用对象拷贝工具原理剖析及性能对比

目前常用的属性拷贝工具,包括 Apache 的 BeanUtils、Spring 的 BeanUtils、Cglib 的 BeanCopier、mapstruct。

  • Apache BeanUtils:BeanUtils 是 Apache commons 组件里面的成员,由 Apache 提供的一套开源 api,用于简化对 javaBean 的操作,能够对基本类型自动转换。

  • Spring BeanUtils:BeanUtils 是 spring 框架下自带的工具,在 org.springframework.beans 包下, spring 项目可以直接使用。

  • Cglib BeanCopier:cglib(Code Generation Library)是一个强大的、高性能、高质量的代码生成类库,BeanCopier 依托于 cglib 的字节码增强能力,动态生成实现类,完成对象的拷贝。

  • mapstruct:mapstruct 是一个 Java 注释处理器,用于生成类型安全的 bean 映射类,在构建时,根据注解生成实现类,完成对象拷贝。

2.1 原理分析

2.1.1 Apache BeanUtils

使用方式:BeanUtils.copyProperties(target, source);BeanUtils.copyProperties 对象拷贝的核心代码如下:

// 1.获取源对象的属性描述PropertyDescriptor[] origDescriptors = this.getPropertyUtils().getPropertyDescriptors(orig);PropertyDescriptor[] temp = origDescriptors;int length = origDescriptors.length;String name;Object value;
// 2.循环获取源对象每个属性,设置目标对象属性值for(int i = 0; i < length; ++i) {
  PropertyDescriptor origDescriptor = temp[i];name = origDescriptor.getName();// 3.校验源对象字段可读切目标对象该字段可写if (!"class".equals(name) && this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {
        try {
  // 4.获取源对象字段值          value = this.getPropertyUtils().getSimpleProperty(orig, name);// 5.拷贝属性          this.copyProperty(dest, name, value);      } catch (NoSuchMethodException var10) {
        }   }}// 1.获取源对象的属性描述PropertyDescriptor[] origDescriptors = this.getPropertyUtils().getPropertyDescriptors(orig);PropertyDescriptor[] temp = origDescriptors;int length = origDescriptors.length;String name;Object value;// 2.循环获取源对象每个属性,设置目标对象属性值for(int i = 0; i < length; ++i) {PropertyDescriptor origDescriptor = temp[i];name = origDescriptor.getName();// 3.校验源对象字段可读切目标对象该字段可写if (!"class".equals(name) && this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {      try {// 4.获取源对象字段值          value = this.getPropertyUtils().getSimpleProperty(orig, name);// 5.拷贝属性          this.copyProperty(dest, name, value);      } catch (NoSuchMethodException var10) {      }   }}

复制代码

循环遍历源对象的每个属性,对于每个属性,拷贝流程为:

  • 校验来源类的字段是否可读 isReadable

  • 校验目标类的字段是否可写 isWriteable

  • 获取来源类的字段属性值 getSimpleProperty

  • 获取目标类字段的类型 type,并进行类型转换

  • 设置目标类字段的值

由于单字段拷贝时每个阶段都会调用 PropertyUtilsBean.getPropertyDescriptor 获取属性配置,而该方法通过 for 循环获取类的字段属性,严重影响拷贝效率。获取字段属性配置的核心代码如下:

PropertyDescriptor[] descriptors = this.getPropertyDescriptors(bean);if (descriptors != null) {
  for (int i = 0; i < descriptors.length; ++i) {
  if (name.equals(descriptors[i].getName())) {
  return descriptors[i];}}}PropertyDescriptor[] descriptors = this.getPropertyDescriptors(bean);if (descriptors != null) {for (int i = 0; i < descriptors.length; ++i) {if (name.equals(descriptors[i].getName())) {return descriptors[i];}}}

复制代码

2.1.2 Spring BeanUtils

使用方式: BeanUtil

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值