概述

众所周知,java世界是由构成的,各种各样的类,提供各种各样的作用,共同创造了一个个的java应用。对象是类的实例,在SpringBoot框架中,对象经常需要拷贝,例如数据库实体拷贝成业务实体,导入实体转换为业务实体,各种数据传输对象之间的拷贝等等。日常开发工作中用到的地方和频率是相当的高。本文就围绕对象拷贝来聊聊常用的姿势(方式)和工具

定义实体类

为了演示对象拷贝将创建几个实体类和几个生成测试数据的方法。

Car

car描述了车辆这个业务对象, 其中包含一些常见的基本数据类型的属性和一个 size类型 的属性,即包含基本数据类型和引用类型,包含子实体。


import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate; @Data
@Accessors(chain = true)
public class Car implements Serializable {
    private Integer id;
    private String name;
    private String brand;
    private String address;
    private Size size;
    private Double cc;
    /**
     * 扭矩
     */
    private Double torque;
    /**
     * 厂商
     */
    private String manufacturer;
    /**
     * 上市时间
     */
    private LocalDate marketDate;
    /**
     * 售价
     */
    private BigDecimal price;
}

size

size描述了车辆大小,具体来说就是长宽高。


import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable; @Data
@Accessors(chain = true)
public class Size  implements Serializable {
    private Double length;
    private Double width;
    private Double height;
}

carInfo

car对象需要拷贝为carInfo对象,他们两个大部分属性都一样,carInfo比car多了 color ,type


package com.ramble.demo.dto;
import com.ramble.demo.model.Size;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate; @Data
@Accessors(chain = true)
public class CarInfo implements Serializable {
    private Integer id;
    private String name;
    private String brand;
    private String address;
    private Size size;
    private Double cc;
    /**
     * 扭矩
     */
    private Double torque;
    /**
     * 厂商
     */
    private String manufacturer;
    /**
     * 上市时间
     */
    private LocalDate marketDate;
    /**
     * 售价
     */
    private BigDecimal price;
    private String color;
    private Integer type;
}

造测试数据

造测试数据用到了一个工具 javaFaker,通过实例化一个Faker 对象可以轻易的生成测试数据:人名、地址、网址、手机号。。。。。。

gav坐标为:


<dependency>
    <groupId>com.github.javafaker</groupId>
    <artifactId>javafaker</artifactId>
    <version>1.0.2</version>
</dependency>

造数据方法很简单,一个批量一个单个,可以通过count来控制造的数据量。


    /**
     * 批量造数据
     *
     * @return
     */
    private List<Car> findCar() {
        Faker faker = new Faker(new Locale("zh-CN"));
        List<Car> list = new ArrayList<>();
        int count = 100000;
        for (int i = 0; i < count; i++) {
            list.add(getCar(faker));
        }
        return list;
    }     /**
     * 造数据 - 单个
     *
     * @param faker
     * @return
     */
    private Car getCar(Faker faker) {
        Car car = new Car();
        car.setId(faker.number().numberBetween(1, 999999999));
        car.setName(faker.name().name());
        car.setBrand(faker.cat().breed());
        car.setAddress(faker.address().fullAddress());
        Size size = new Size();
        size.setLength(faker.number().randomDouble(7, 4000, 7000));
        size.setWidth(faker.number().randomDouble(7, 4000, 7000));
        size.setHeight(faker.number().randomDouble(7, 4000, 7000));
        car.setSize(size);
        car.setCc(faker.number().randomDouble(7, 1000, 12000));
        car.setTorque(faker.number().randomDouble(1, 100, 70000));
        car.setManufacturer(faker.name().name());
        Date date = faker.date().birthday();
        Instant instant = date.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDate localDate = instant.atZone(zone).toLocalDate();
        car.setMarketDate(localDate);
        car.setPrice(BigDecimal.valueOf(faker.number().randomDigit()));
        return car;
    }

Spring BeanUtils

这么常用的功能官方肯定已经集成了,对对对,就是BeanUtils.copyProperties了。顺便说一句,遇到找工具类的时候不要盲目baidu,不妨先看看springframework.util这个包下面的东西。

下面看看Spring的BeanUtils.copyProperties用法举例


Faker faker = new Faker(new Locale("zh-CN"));
Car car = getCar(faker);
CarInfo carInfo = new CarInfo();
BeanUtils.copyProperties(car, carInfo);
  • 使用反射实现两个对象的拷贝
  • 不会对类型进行转换,如source对象有一个integer类型的id属性,target对象有一个string类型的id属性,拷贝之后,target对象为null;同理integer 无法拷贝到long
  • 官方出品,无需引入其他依赖,推荐指数8颗星

Apache BeanUtils

apache-common系列的东西,java开发多多少少会用到一些,它本身也积累了很多经验提供一些有用并常用的工具和组件。

针对BeanUtils 使用前需要引入pom


<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

下面看看Apache的BeanUtils.copyProperties 用法举例


Faker faker = new Faker(new Locale("zh-CN"));
Car car = getCar(faker);
CarInfo carInfo = new CarInfo();
try {
org.apache.commons.beanutils.BeanUtils.copyProperties(carInfo, car);
} catch (IllegalAccessException e) {
    throw new RuntimeException(e);
} catch (InvocationTargetException e) {
    throw new RuntimeException(e);
}
  • 强制要处理异常
  • source对象和target对象相对Spring来说是颠倒的
  • 通过反射实现
  • 不会对类型进行转换,如source对象有一个integer类型的id属性,target对象有一个string类型的id属性,拷贝之后,target对象为null;同理integer 无法拷贝到long
  • 性能稍微逊色与Spring,推荐指数7颗星

Cglib BeanCopier

BeanCopier使用起来稍微有点复杂,需要先实例化一个BeanCopier对象,然后再调用其copy方法完成对象拷贝

使用前需要引入如下pom


<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.0</version>
</dependency>

下面看看 Cglib 的 BeanCopier 用法举例


Faker faker = new Faker(new Locale("zh-CN"));
Car car = getCar(faker);
CarInfo item = new CarInfo();
inal BeanCopier copier = BeanCopier.create(Car.class, CarInfo.class, false);
copier.copy(car, item, null);
  • BeanCopier虽然使用复杂,且要引入依赖,但是性能出众
  • 对于数据量小的情况不没有必要使用这个,推荐指数5颗星
  • 对于数据量大,比如5W个对象拷贝为另外一个对象,这种场景虽然少但是一旦发生了Spring BeanUtils处理起来就非常耗时,这种情况下推荐指数9颗星

MapStruct

MapStruct 是一个代码生成器,它基于约定生成的映射代码使用的是普通的方法调用,因此快速、类型安全且易于理解。MapStruct 本质没有太多科技与狠活,就是帮助开发人员编写getA,setB这样的样板代码,并提供扩展点,比如将carType映射为type。

使用前需要引入如下pom


<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.0.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.0.Final</version>
</dependency>

下面看看MapStruct用法举例

使用前需要先定义一个接口,编写转换逻辑,而后通过调用接口方法获取转换后的结果。

CarInfoMapper


import com.ramble.demo.dto.CarInfo;
import com.ramble.demo.model.Car;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers; @Mapper
public interface CarInfoMapper {
    CarInfoMapper INSTANCT = Mappers.getMapper(CarInfoMapper.class);
    CarInfo carToCarInfo(Car car);
}
  • Mapper:将此接口标注为用来转换bean的接口,并在编译过程中生成相应的实现类
  • carToCarInfo,声明一个转换方法,并将source作为方法入参,target作为方法出参
  • INSTANCT,按照约定,接口声明一个INSTANCT成员,提供给访问者消费
  • 一个接口可以有多个转换方法
  • 可通过在方法上添加@Mapping注解,将不同属性名子的属性进行转换
  • 可以将枚举类型转换为字符串
  • 性能出众,术业有专攻,在处理复杂转换和大数据量的时候很有必要
  • 因为要引入依赖并且需要单独编写接口,所以对于简单的转换还是推荐Spring,这种情况下推荐指数5颗星
  • 如果在大数据量的情况,并且转换相对复杂,推荐指数10颗星

调用 CarInfoMapper 进行对象拷贝


Faker faker = new Faker(new Locale("zh-CN"));
Car car = getCar(faker);
CarInfo carInfo = CarInfoMapper.INSTANCT.carToCarInfo(car);

性能测试

原则上, 性能测试是多维度的,速度、CPU消耗、内存消耗等等,本文仅考虑速度,只为说明问题,不为得到测试数据

测试代码逻辑,使用for结合faker生成一个大的List,然后用上述4种方式做属性拷贝。使用StopWatch记录程序运行时间。

测试代码如下:


/**
 * 测试4种方式的处理速度
 */
@GetMapping("/test1")
public void test1() {
    Faker faker = new Faker(new Locale("zh-CN"));
    List<Car> carList = findCar();
    StopWatch sw = new StopWatch("对象拷贝速度测试");
    sw.start("方式1:Spring BeanUtils.copyProperties");
    List<CarInfo> list1 = new ArrayList<>();
    carList.forEach(x -> {
        CarInfo item = new CarInfo();
        BeanUtils.copyProperties(x, item);
        item.setColor(faker.color().name());
        item.setType(faker.number().randomDigit());
        list1.add(item);
    });
    sw.stop();
    sw.start("方式2:apache BeanUtils.copyProperties");
    List<CarInfo> list2 = new ArrayList<>();
    carList.forEach(x -> {
        CarInfo item = new CarInfo();
        try {
            org.apache.commons.beanutils.BeanUtils.copyProperties(item, x);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        item.setColor(faker.color().name());
        item.setType(faker.number().randomDigit());
        list2.add(item);
    });
    sw.stop();
    sw.start("方式3:BeanCopier");
    List<CarInfo> list3 = new ArrayList<>();
    carList.forEach(x -> {
        CarInfo item = new CarInfo();
        final BeanCopier copier = BeanCopier.create(Car.class, CarInfo.class, false);
        copier.copy(x, item, null);
        item.setColor(faker.color().name());
        item.setType(faker.number().randomDigit());
        list3.add(item);
    });
    sw.stop();
    sw.start("方式4:MapStruct");
    List<CarInfo> list4 = new ArrayList<>();
    carList.forEach(x -> {
        CarInfo item = CarInfoMapper.INSTANCT.carToCarInfo(x);
        item.setColor(faker.color().name());
        item.setType(faker.number().randomDigit());
        list4.add(item);
    });
    sw.stop();
    String s = sw.prettyPrint();
    System.out.println(s);
    double totalTimeSeconds = sw.getTotalTimeSeconds();
    System.out.println("总耗时:" + totalTimeSeconds);
}

测试结果如下

10W 数据量


StopWatch '对象拷贝速度测试': running time = 5382233200 ns
---------------------------------------------
ns % Task name
---------------------------------------------
2336099000 043% 方式1:Spring BeanUtils.copyProperties
2322316400 043% 方式2:apache BeanUtils.copyProperties
442981700 008% 方式3:BeanCopier
280836100 005% 方式4:MapStruct 总耗时:5.3822332

100W 数据量


StopWatch '对象拷贝速度测试': running time = 58689078300 ns
---------------------------------------------
ns % Task name
---------------------------------------------
25199532100 043% 方式1:Spring BeanUtils.copyProperties
27179965900 046% 方式2:apache BeanUtils.copyProperties
3629756500 006% 方式3:BeanCopier
2679823800 005% 方式4:MapStruct 总耗时:58.6890783

深拷贝or浅拷贝

上述四种方式均为浅拷贝。可通过如下测试代码验证


 /**
 * 测试4种方式是浅拷贝还是深拷贝
 */
@GetMapping("/test2")
public void test2() {
    Faker faker = new Faker(new Locale("zh-CN"));
    Car car = getCar(faker);
    log.info("car={}", JSON.toJSONString(car));
    CarInfo carInfo = new CarInfo();
    //方式1
//        BeanUtils.copyProperties(car, carInfo);
//        Size sizeNew = car.getSize();
//        sizeNew.setLength(0d);
//        sizeNew.setWidth(0d);
//        sizeNew.setHeight(0d);
////        Size s = new Size();
////        s.setHeight(10d);
////        car.setSize(s);
//        car.setName("新名字");
//        log.info("carInfo={}", JSON.toJSONString(carInfo));
        //方式2
//        try {
//            org.apache.commons.beanutils.BeanUtils.copyProperties(carInfo, car);
//        } catch (IllegalAccessException e) {
//            throw new RuntimeException(e);
//        } catch (InvocationTargetException e) {
//            throw new RuntimeException(e);
//        }
//        Size sizeNew = car.getSize();
//        sizeNew.setLength(0d);
//        sizeNew.setWidth(0d);
//        sizeNew.setHeight(0d);
//
//        car.setName("新名字");
//        log.info("carInfo={}", JSON.toJSONString(carInfo));
        //方式3
//        final BeanCopier copier = BeanCopier.create(Car.class, CarInfo.class, false);
//        copier.copy(car, carInfo, null);
//        Size sizeNew = car.getSize();
//        sizeNew.setLength(0d);
//        sizeNew.setWidth(0d);
//        sizeNew.setHeight(0d);
//
//        car.setName("新名字");
//        log.info("carInfo={}", JSON.toJSONString(carInfo));
        //方式4
        carInfo = CarInfoMapper.INSTANCT.carToCarInfo(car);
        Size sizeNew = car.getSize();
        sizeNew.setLength(0d);
        sizeNew.setWidth(0d);
        sizeNew.setHeight(0d);
        car.setName("新名字");
        log.info("carInfo={}", JSON.toJSONString(carInfo));
}

验证逻辑为,在完成copy后修改car.size 的值,发现carInfo.size随之改变,说明是浅拷贝。

SpringBoot对象拷贝的更多相关文章

  1. 《Python CookBook2》 第四章 Python技巧 对象拷贝 && 通过列表推导构建列表

    (先学第四章) 对象拷贝 任务: Python通常只是使用指向原对象的引用,并不是真正的拷贝. 解决方案: >>> a = [1,2,3] >>> import c ...

  2. python中的对象拷贝

    python中.进行函数參数传递或者返回值时,假设是一般的变量,会拷贝传递.假设是列表或字典则是引用传递.那python怎样对列表和字典进行拷贝传递呢:标准库的copy模块提供了两个方法:copy和d ...

  3. OC中对象拷贝概念

    OC中的对象拷贝概念,这个对于面向对象语言中都会有这种的问题,只是不同的语言有不同的解决方式:C++中有拷贝构造函数,Java中需要实现Cloneable接口,在clone方法中进行操作.但是不过OC ...

  4. C# 对象拷贝问题 =等同于浅拷贝

    大家都知道,在C#中变量的存储分为值类型和引用类型两种,而值类型和引用类型在数值变化是产生的后果是不一样的,值类型我们可以轻松实现数值的拷贝,那么引用类型呢,在对象拷贝上存在着一定的难度.     下 ...

  5. 也说Javascript对象拷贝及疑问

    一.浅拷贝 当我们需要将一个对象拷贝至另一个对象时,我们一般会这么实现 function shadowCopy(source,target){ var target=target||{}; for(v ...

  6. Java Object 对象拷贝答疑

    Java Object 对象拷贝答疑 @author ixenos 摘要:在对象的clone过程需要注意的几点.关于关键字this.super 关于clone[对象拷贝] 在实际编程过程,有时候我们会 ...

  7. Java Object 对象拷贝

    Java Object 对象拷贝 @author ixenos JAVA 对象拷贝 Java里的clone分为:  1.浅拷贝:浅复制仅仅复制所考虑的对象,而不复制它所引用的对象,Object类里的c ...

  8. JavaScript 对象拷贝研究

    介绍一下JavaScript里面的一些对象拷贝的方法 浅拷贝 深拷贝 利用序列化进行对象拷贝

  9. Java 开发中的对象拷贝

    前言 在 Java 开发中,很多时候需要将两个属性基本相同的对象进行属性复制,比如 DO 转 VO等等. 本文主要介绍自己实现的简易拷贝工具类与 Spring 提供的属性拷贝的对比. Spring 提 ...

  10. 【转】python中的对象拷贝

    转自:https://www.cnblogs.com/bhlsheji/p/5352330.html python中.进行函数參数传递或者返回值时,假设是一般的变量,会拷贝传递.假设是列表或字典则是引 ...

随机推荐

  1. Electron创建项目并打包生成exe

    安装nodejs 访问这个网站去下载 http://nodejs.cn/download/ 创建项目 创建项目 git clone https://github.com/electron/electr ...

  2. Avalonia开发(二)项目结构解析

    一.前言 在Avalonia开发(一)环境搭建 文章中介绍了Avalonia的介绍.开发环境的搭建.项目创建,以及项目FirstAvaloniaApp项目结构的介绍.本篇文章将介绍各平台的项目介绍. ...

  3. 一文给你讲清楚BeanFactory 和 FactoryBean 的关联与区别

    本文分享自华为云社区 <BeanFactory 和 FactoryBean 的关联与区别>,作者:战斧. 一.概括性的回答 两者其实都是Spring提供的接口,如下 public inte ...

  4. TCP协议的秘密武器:流量控制与拥塞控制

    TCP可靠性传输 相信大家都熟知TCP协议作为一种可靠传输协议,但它是如何确保传输的可靠性呢? 要实现可靠性传输,需要考虑许多因素,比如数据的损坏.丢失.重复以及分片顺序混乱等问题.如果不能解决这些问 ...

  5. WPF中以MVVM方式,实现RTSP视频播放

    前言视频播放在上位机开发中经常会遇到,基本上是两种常见的解决方案 1.采用厂家提供的sdk和前端控件进行展示,常见的海康/大华都提供了相关sdk及文档 2.开启相机onvif协议,捅过rtsp视频流进 ...

  6. Redis最常见的5种应用场景

    Redis作为当今最流行的内存数据库,已经成为服务端加速的必备工具之一.对于Redis为什么那么快?以及Redis采用单线程,但为什么反而获得更高的性能的疑问,在之前的Redis为什么那么快?一文中, ...

  7. MySQL系列之MyCat——架构图、准备、安装、配置文件、垂直分表、MyCAT核心特性——分片(水平拆分)、范围分片、枚举分片、Mycat全局表、E-R分片

    文章目录 1. MyCAT基础架构图 2. MyCAT基础架构准备 2.1 环境准备: 2.2 删除历史环境: 2.3 创建相关目录初始化数据 2.4 准备配置文件和启动脚本 2.5 修改权限,启动多 ...

  8. ChatGPT 是如何产生心智的?

    一.前言 - ChatGPT真的产生心智了吗? 来自斯坦福大学的最新研究结论,一经发出就造成了学术圈的轰动,"原本认为是人类独有的心智理论(Theory of Mind,ToM),已经出现在 ...

  9. 一次考试的T3

    啊这感觉不太可做观察性质,发现这个字符串只由ABC构成这个性质必须利用仅仅由3种字符组成意味着什么呢?这个字符串只有种可能性这个有什么用呢?只是说明暴力枚举的时间复杂度会小一些而已.不止是这些. 首先 ...

  10. 高性能日志脱敏组件:已支持 log4j2 和 logback 插件

    项目介绍 日志脱敏是常见的安全需求.普通的基于工具类方法的方式,对代码的入侵性太强,编写起来又特别麻烦. sensitive提供基于注解的方式,并且内置了常见的脱敏方式,便于开发. 同时支持 logb ...