你还在用BeanUtils进行对象属性拷贝?


你还在用BeanUtils进行对象属性拷贝?

文章插图
作者:Van_Fan
在做业务的时候 , 为了隔离变化 , 我们会将DAO查询出来的DO和对前端提供的DTO隔离开来 。大概90%的时候 , 它们的结构都是类似的;但是我们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代码 , 于是我们需要简化对象拷贝方式 。
大多时候时候使用的是Apache或Spring``BeanUtils , 今天 , 我们来看一下一个更高效的属性拷贝方式:BeanCopier 。
一、背景1.1 对象拷贝概念JAVA中 , 数据类型分为值类型(基本数据类型)和引用类型 , 值类型包括int、double、byte、boolean、char等简单数据类型 , 引用类型包括类、接口、数组等复杂类型 。
对象拷贝分为浅拷贝(浅克隆)与深拷贝(深克隆) 。
  • 浅拷贝与深拷贝差异

你还在用BeanUtils进行对象属性拷贝?

文章插图
 
1.2 示例前准备
  • 源对象属性类UserDO.class(以下示例 , 源对象都用这个)
@Datapublic class UserDO {private int id;private String userName;/*** 以下两个字段用户模拟自定义转换*/private LocalDateTime gmtBroth;private BigDecimal balance;public UserDO(Integer id, String userName, LocalDateTime gmtBroth, BigDecimal balance) {this.id = id;this.userName = userName;this.gmtBroth = gmtBroth;this.balance = balance;}}
  • 造数据工具类DataUtil.class
public class DataUtil {/*** 模拟查询出一条数据* @return*/public static UserDO createData() {return new UserDO(1, "Van", LocalDateTime.now(),new BigDecimal(100L));}/*** 模拟查询出多条数据* @param num 数量* @return*/public static List<UserDO> createDataList(int num) {List<UserDO> userDOS = new ArrayList<>();for (int i = 0; i < num; i++) {UserDO userDO = new UserDO(i+1, "Van", LocalDateTime.now(),new BigDecimal(100L));userDOS.add(userDO);}return userDOS;}}二、对象拷贝之BeanUtils
Apache和Spring均有BeanUtils工具类 ,  Apache的BeanUtils稳定性与效率都不行;Spring的BeanUtils比较稳定 , 不会因为量大了 , 耗时明显增加 , 故一般都使用Spring的BeanUtils 。
2.1 源码解读Spring中的BeanUtils , 其中实现的方式很简单 , 就是对两个对象中相同名字的属性进行简单get/set , 仅检查属性的可访问性 。
BeanUtils 源码
可以看到, 成员变量赋值是基于目标对象的成员列表, 并且会跳过ignore的以及在源对象中不存在的, 所以这个方法是安全的, 不会因为两个对象之间的结构差异导致错误, 但是必须保证同名的两个成员变量类型相同 。
2.2 示例@Slf4jpublic class BeanUtilsDemo {public static void main(String[] args) {long start = System.currentTimeMillis();UserDO userDO = DataUtil.createData();log.info("拷贝前,userDO:{}", userDO);UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(userDO,userDTO);log.info("拷贝后,userDO:{}", userDO);}}
  • 结果
18:12:11.734 [main] INFO cn.van.parameter.bean.copy.demo.BeanUtilsDemo - 拷贝前,userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T18:12:11.730, balance=100)18:12:11.917 [main] INFO cn.van.parameter.bean.copy.demo.BeanUtilsDemo - 拷贝后,userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T18:12:11.730, balance=100)三、对象拷贝之BeanCopierBeanCopier是用于在两个bean之间进行属性拷贝的 。BeanCopier支持两种方式:
  1. 一种是不使用Converter的方式 , 仅对两个bean间属性名和类型完全相同的变量进行拷贝;
  2. 另一种则引入Converter , 可以对某些特定属性值进行特殊操作 。
3.1 基本使用
  • 依赖
<dependency><groupId>cglib</groupId><artifactId>cglib-nodep</artifactId><version>3.3.0</version></dependency>**注意:**该依赖非必须 , 因为Spring中已经集成了cglib,博主使用的就是org.springframework.cglib.beans.BeanCopier 。
3.1.1 属性名称、类型都相同
  • 目标对象属性类
@Datapublic class UserDTO {private int id;private String userName;}
  • 测试方法
/** * 属性名称、类型都相同(部分属性不拷贝) */private static void normalCopy() { // 模拟查询出数据UserDO userDO = DataUtil.createData();log.info("拷贝前:userDO:{}", userDO);// 第一个参数:源对象 ,  第二个参数:目标对象 , 第三个参数:是否使用自定义转换器(下面会介绍) , 下同BeanCopier b = BeanCopier.create(UserDO.class, UserDTO.class, false);UserDTO userDTO = new UserDTO();b.copy(userDO, userDTO, null);log.info("拷贝后:userDTO:{}", userDTO);}


推荐阅读