Spring学习笔记(二)【CGLIB浅拷贝BeanCopier的使用和详解】

CGLIB浅拷贝BeanCopier的使用和详解

一、bean拷贝工具


bean拷贝工具类比较

常用的bean拷贝工具类当中,主要有Apache提供的beanUtils、Spring提供的beanUtils、Cglib提供的beanCopier,性能上分析如下表所示(该表来自网上的数据)

方法归属单次耗时[ms]100万次耗时[ms]平均耗时[ms]
BeanUtilscopyPropertiesApache0.31019630253.1400570.030253
BeanUtilscopyPropertiesSpring0.0720822155.7443780.002155
BeanCopiercopyCglib0.00507158.4152010.000058

上表当中可以发现三者性能:cglib > spring > hutool

本次所讲的内容是关于BeanCopier类的使用,当我们需要拷贝大量的数据,使用这个是最快的,而对于拷贝少量对象时,和其它的拷贝工具类速度也差不多,现在CGLIB也并入Spring,所以在Spring项目中可以直接使用它,不需要添加其他maven

  • 原依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

优点:

  • 拷贝速度快

缺点:

  • 无法实现null字段跳过
  • 每次使用都需要调用create函数,如果上述实验把create放到循环里,结果会变成5s
  • 无法实现复杂类型的拷贝(如List)

在使用他的时候,我们需要先创建一个BeanCopier对象,源代码如下

public static BeanCopier create(Class source, Class target, boolean useConverter) {
    Generator gen = new Generator();
    gen.setSource(source);
    gen.setTarget(target);
    gen.setUseConverter(useConverter);
    return gen.create();
}

create函数参数解析:

  • 第一个参数source:需要拷贝的对象
  • 第二个参数target:拷贝后的目标对象类型
  • 第三个参数useConverter:用户控制转换器,是否使用自定义的转换器

useConverter控制权限转换:

这个是用户控制转换器,如果设置为false,它会对拷贝的对象和被拷贝的对象的类型进行判断,如果类型不同就不会拷贝,如果要使他会拷贝,就需要设置为true,自己拿到控制权对其进行处理,一般情况下使用的都是false

二、自定义转换器演示


首先我们有两个类,属性都相同,但有一个类中其中一个属性类型与另外一个类对应的属性类型不一致,情况如下

@Data
public class User {
    private String username;
    private Integer password;
}
@Data
public class UserDTO {
    private String username;
    private String password;
}
  • 执行代码
User user = new User();
user.setUsername("vinjcent");
user.setPassword("123456");
UserDTO userDTO = BeanCopierUtils.copy(user, UserDTO.class);

System.out.println("源对象===>" + user);
System.out.println("拷贝对象===>" + userDTO);

从控制台结果可以看出,拷贝的对象的password是空的,因为类型不同

在这里插入图片描述

如果要不同类型都拷贝上去,就要设置useConverter的控制权设置为true,并且实现Convert类,重写代码如下(注意,这是全类属性进行转换

package com.vinjcent.api.utils;

import org.springframework.cglib.core.Converter;

/**
 * @author vinjcent
 * @description bean拷贝转换器(不推荐使用,尽量设计时保持类型一致)
 * @since 2023/3/28 15:06
 */
public class ConverterSetting implements Converter {

    /**
     * @param source  源对象值
     * @param target  目标类型
     * @param setFunc 实体类的set方法
     * @return
     */
    @Override
    public Object convert(Object source, Class target, Object setFunc) {
        if (source instanceof Enum) {
            return source;
        } else {
            return source.toString();
        }
    }
}

convert函数参数解析:

  • 第一个参数source:源类的值
  • 第二个参数target:目标类类型
  • 第三个参数setFunc:源类的set方法

使用自定义转换器convert为true的代码

    /**
     * 自定义类型转换器
     *
     * @param source    源对象
     * @param target    目标类
     * @param converter 转换器
     */
    public static void copy(Object source, Object target, Converter converter) {
        BeanCopier beanCopier = BeanCopier.create(source.getClass(), target.getClass(), true);
        beanCopier.copy(source, target, converter);
    }
  • 测试代码以及结果
User user = new User();
user.setUsername("vinjcent");
user.setPassword(123456);
UserDTO userDTO = BeanCopierUtils.copy(user, UserDTO.class, new ConverterSetting());
System.out.println("源对象===>" + user);
System.out.println("拷贝对象===>" + userDTO);

在这里插入图片描述

三、目标类值的覆盖


在使用bean拷贝过程中,假如需要将源对象的属性值拷贝给另外一个有相同属性的目标对象,那么会进行值得覆盖,如果目标对象当中存在源对象都没有的属性,则不会进行覆盖,否则进行覆盖

修改对应的代码,如下

  • UserDTO
@Data
public class UserDTO {
    private String username;
    private String password;
    private Double weight;
}
  • 测试用例与结果
User user = new User();
user.setUsername("vinjcent");
user.setPassword(123456);
UserDTO userDTO = new UserDTO("Totoro", "1008611", 55.55);
BeanCopierUtils.copy(user, userDTO);
System.out.println("源对象===>" + user);
System.out.println("拷贝对象===>" + userDTO);

在这里插入图片描述

四、Map优化创建BeanCopier


由于每次使用工具类进行拷贝都需要调用create()函数创建一个BeanCopier对象,可以将创建过的BeanCopier实例可以放到缓存中,下次相同的转换可以直接获取,提升性能

在这里,以源类名 + “_” + 目标类名作为key,对应创建好的BeanCopier作为value

package com.vinjcent.api.utils;

import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.core.Converter;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author vinjcent
 * @description cglib包中的bean拷贝(性能优于Spring当中的BeanUtils)
 * @since 2023/3/28 14:22
 */
public class BeanCopierUtils {


    /**
     * 创建一个map来存储BeanCopier缓存
     */
    private static final Map<String, BeanCopier> BEAN_COPIER_MAP = new ConcurrentHashMap<>();

    /**
     * 深拷贝,我们可以直接传实例化的拷贝对象和被实例化的拷贝对象进行深拷贝
     *
     * @param source 源对象
     * @param target 目标类
     */
    private static void copy(Object source, Object target) {
        // 获取当前两者转换map对应的key
        String key = getKey(source, target);
        BeanCopier beanCopier;
        // 判断键是否存在,不存在就将BeanCopier插入到map里,存在就直接获取
        if (!BEAN_COPIER_MAP.containsKey(key)) {
            // 参数1: 源对象类   参数2: 目标对象类   参数3: 是否使用自定义转换器
            beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);
            BEAN_COPIER_MAP.put(key, beanCopier);
        } else {
            beanCopier = BEAN_COPIER_MAP.get(key);
        }
        // 自定义转换器可在copy函数当中的第三个参数设置
        beanCopier.copy(source, target, null);
    }

    /**
     * 深拷贝
     *
     * @param source 源对象
     * @param target 目标类
     * @param <T>    目标类型
     * @return 单个目标类
     */
    public static <T> T copy(Object source, Class<T> target) {
        // 如果源对象为空,结束
        if (source == null) {
            return null;
        }
        // 用来判断目标类型空指针异常
        Objects.requireNonNull(target);
        T result = null;
        try {
            result = target.newInstance();
            copy(source, result);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * List深拷贝
     *
     * @param sources 源集合
     * @param target  目标类
     * @param <S>     源类型
     * @param <T>     目标类型
     * @return 目标类集合
     */
    public static <S, T> List<T> copyList(List<S> sources, Class<T> target) {
        // 用来判断目标类型空指针异常
        Objects.requireNonNull(target);
        return sources.stream().map(src -> copy(src, target)).collect(Collectors.toList());

    }

    /**
     * 自定义类型转换器
     *
     * @param source    源对象
     * @param target    目标类
     * @param converter 转换器
     */
    private static void copy(Object source, Object target, Converter converter) {
        if (!Objects.isNull(converter)) {
            BeanCopier beanCopier = BeanCopier.create(source.getClass(), target.getClass(), true);
            beanCopier.copy(source, target, converter);
        } else {
            copy(source, target);
        }
    }

    /**
     * 自定义类型转换器
     *
     * @param source    源对象
     * @param target    目标类
     * @param converter 转换器
     * @param <T>       目标类型
     * @return 单个目标类
     */
    public static <T> T copy(Object source, Class<T> target, Converter converter) {
        // 如果源对象为空,结束
        if (source == null) {
            return null;
        }
        // 用来判断目标类型空指针异常
        Objects.requireNonNull(target);
        T result = null;
        try {
            result = target.newInstance();
            copy(source, result, converter);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 获取Map中的Key
     *
     * @param source 源对象
     * @param target 目标类
     * @return 源对象与目标类名字的拼接
     */
    private static String getKey(Object source, Object target) {
        return source.getClass().getName() + "_" + target.getClass().getName();
    }

}