• 中文
    • English
  • 注册
  • 查看作者
  • Java对象拷贝原理剖析及最佳实践

    作者:宁海翔

    1 前言

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

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

    1.1 深拷贝

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

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

    • 构造函数

    • Serializable序列化

    • 实现Cloneable接口

    • JSON序列化

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

    1.2 浅拷贝

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

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

    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 对象拷贝的核心代码如下:

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

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

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

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

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

    • 设置目标类字段的值

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

    2.1.2 Spring BeanUtils

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

    拷贝流程简要描述如下:

    • 获取目标类的所有属性描述

    • 循环目标类的属性值做以下操作

    • 获取目标类的写方法

    • 获取来源类的该属性的属性描述(缓存获取)

    • 获取来源类的读方法

    • 读来源属性值

    • 写目标属性值

    与Apache BeanUtils的属性拷贝相比,Spring通过Map缓存,避免了类的属性描述重复获取加载,通过懒加载,初次拷贝时加载所有属性描述。

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

    2.1.3 Cglib BeanCopier

    使用方式:

    create调用链如下:

    BeanCopier.create-> BeanCopier.Generator.create-> AbstractClassGenerator.create->DefaultGeneratorStrategy.generate-> BeanCopier.Generator.generateClass

    BeanCopier 通过cglib动态代理操作字节码,生成一个复制类,触发点为BeanCopier.create

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

    2.1.4 mapstruct

    使用方式:

    • 引入pom依赖

    • 声明转换接口

    mapstruct基于注解,构建时自动生成实现类,调用链如下:MappingProcessor.process -> MappingProcessor.processMapperElementsMapperCreationProcessor.process:生成实现类MapperMapperRenderingProcessor:将实现类mapper,写入文件,生成impl文件使用时需要声明转换接口,例如:

    生成的实现类如下:

    2.2 性能对比

    以航空业务系统中发货任务po到dto转换为例,随着拷贝数据量的增大,研究拷贝数据耗时情况

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

    2.3 拷贝选型

    经过以上分析,随着数据量的增大,耗时整体呈上升趋势

    • 整体情况下,Apache BeanUtils的性能最差,日常使用过程中不建议使用

    • 在数据规模不大的情况下,spring、cglib、mapstruct差异不大,spring框架下建议使用spring的beanUtils,不需要额外引入依赖包

    • 数据量大的情况下,建议使用cglib和mapstruct

    • 涉及大量数据转换,属性映射,格式转换的,建议使用mapstruct

    3 最佳实践

    3.1 BeanCopier

    使用时可以使用map缓存,减少同一类对象转换时,create次数

    3.2 mapstruct

    mapstruct支持多种形式对象的映射,主要有下面几种

    • 基本映射

    • 映射表达式

    • 多个对象映射到一个对象

    • 映射集合

    • 映射map

    • 映射枚举

    • 嵌套映射

    4 总结

    以上就是我在使用对象拷贝过程中的一点浅谈。在日常系统开发过程中,要深究底层逻辑,哪怕发现一小点的改变能够使我们的系统更加稳定、顺畅,都是值得我们去改进的。

    最后,希望随着我们的加入,系统会更加稳定、顺畅,我们会变得越来越优秀。

  • 0
  • 0
  • 0
  • 46
  • 请登录之后再进行评论

    登录
  • 任务
  • 实时动态
  • 发布
  • 单栏布局 侧栏位置: