BeanCopier的工具类

平时对象参数拷贝时,顺手使用的都是Spring 提供的 BeanUtils。究其原因,更多都是因为它方便,好使。

但是大家都知道cglib的 BeanCopier 通过字节码技术,在大多数没有Converter的情况下,拷贝效率更高。只是因为每次使用都得BeanCopier.create,创建一个BeanCopier对象,麻烦,代码还不好看。

因此,就有了这个工具类。使用和Spring工具包一样的代码(重载方法甚至比Spring的更丰富),获得更好的效率。实现中通过对BeanCopier对象的一个Map缓存,会使得其效率更高一丢丢。

至于具体的速度对比,网上有很多的实例,我也不做对比测试了。代码如下,如有欠缺的地方,希望留言指正,不胜感激。


import com.google.common.collect.Maps;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.core.Converter;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Objects;

/**
 * @author imyzt
 * @date 2020/01/15
 * @description bean拷贝缓存工具类
 * 合理使用, 注意Map大小
 */
@UtilityClass
@Slf4j
public class BeanCopierUtils {

    private static final Map<String, BeanCopier> BEAN_COPIER_MAP = Maps.newConcurrentMap();

    /**
     * 使用BeanCopier拷贝对象属性
     * 1. target不能使用链式调用 {@link lombok.experimental.Accessors#chain}
     * 2. 只会拷贝source,target属性类型和名称完全一致的字段
     * @param source 源
     * @param target 目标
     * @param useCache 是否使用缓存
     * @param converter converter对象
     * @param <S> 源对象泛型
     * @param <T> 目标对象泛型
     * @return 返回target对象
     */
    public <S, T> T copy(S source, T target, boolean useCache, Converter converter) {

        requireNonNull(source, target);
        boolean useConverter = Objects.nonNull(converter);

        Class<?> targetClass = target.getClass();

        String key = getKey(source, targetClass);

        BeanCopier beanCopier = getBeanCopier(useCache, key, createBeanCopier(source, targetClass, useConverter));
        beanCopier.copy(source, target, converter);
        return target;
    }

    /**
     * 使用BeanCopier拷贝对象属性
     * 1. target不能使用链式调用 {@link lombok.experimental.Accessors#chain}
     * 2. 只会拷贝source,target属性类型和名称完全一致的字段
     * @param source 源
     * @param targetClass 目标类对象
     * @param useCache 是否使用缓存
     * @param converter converter对象
     * @param <S> 源对象泛型
     * @param <T> 目标对象泛型
     * @return 返回target对象
     */
    public <S, T> T copy(S source, Class<T> targetClass, boolean useCache, Converter converter) {

        requireNonNull(source, targetClass);
        boolean useConverter = Objects.nonNull(converter);

        String key = getKey(source, targetClass);

        BeanCopier beanCopier = getBeanCopier(useCache, key, createBeanCopier(source, targetClass, useConverter));

        T instance;
        try {
            instance = targetClass.getDeclaredConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            throw new IllegalArgumentException();
        }
        beanCopier.copy(source, instance, converter);
        return instance;
    }

    private static BeanCopier getBeanCopier(boolean useCache, String key, BeanCopier beanCopier2) {
        BeanCopier beanCopier;
        if (useCache) {
            beanCopier = BEAN_COPIER_MAP.computeIfAbsent(key, k -> beanCopier2);
        } else {
            beanCopier = beanCopier2;
        }
        return beanCopier;
    }

    private static <S> void requireNonNull(S source, Object targetClass) {
        Objects.requireNonNull(source, "源对象不能为空");
        Objects.requireNonNull(targetClass, "目标对象不能为空");
    }

    public <S, T> T copy(S source, T target) {
        return copy(source, target, true, null);
    }

    public <S, T> T copy(S source, Class<T> targetClass) {
        return copy(source, targetClass, true, null);
    }

    public <S, T> T copy(S source, Class<T> targetClass, boolean useCache) {
        return copy(source, targetClass, useCache, null);
    }

    public <S, T> T copy(S source, Class<T> targetClass, Converter converter) {
        return copy(source, targetClass, true, converter);
    }

    public <S, T> T copy(S source, T target, boolean useCache) {
        return copy(source, target, useCache, null);
    }

    public <S, T> T copy(S source, T target, Converter converter) {
        return copy(source, target, true, converter);
    }

    private static <S, T> BeanCopier createBeanCopier(S source, Class<T> target, boolean useConverter) {
        return BeanCopier.create(source.getClass(), target, useConverter);
    }

    private static <S, T> String getKey(S source, Class<T> targetClass) {
        return source.getClass().getName() + ":" + targetClass.getName();
    }
}

在使用时,有两点需要注意的地方:

  1. target不能使用lombok等工具的链式调用,只有符合标准的set方法,cglib才会调用赋值。
  2. 只会拷贝source,target类型和名称完全一致的属性。