比Spring的BeanUtils深拷贝更快的Util

320 阅读4分钟

大多时候时候使用的是ApacheSpring BeanUtils,今天,我们来看一下一个更高效的属性拷贝方式:BeanCopier

github.com/cglib/cglib github.com/cglib/cglib…

首先梳理出来现在有哪些对象拷贝的方式:

  1. Apache的BeanUtils:BeanUtils是Apache commens组件里面的成员,由Apache提供的一套开源api,用于简化对javaBean的操作,能够对基本类型自动转换。
  2. Spring的BeanUtils:BeanUtils是Spring框架提供的对Java反射和自省API的包装。其主要目的是利用反射机制对JavaBean的属性进行处理。
  3. Mapstruct:MapStruct是一个Java 注释处理器,用于为Java Bean类生成类型安全和高性能的映射器。它使您不必手工编写映射代码,这是一个繁琐且容易出错的任务。该生成器具有合理的默认值和许多内置的类型转换,但是在配置或实现特殊行为时,它会自动退出。与运行时工作的映射框架相比,MapStruct具有以下优点:通过使用普通方法调用而不是反射来快速执行编译时类型安全。只能映射彼此映射的对象和属性,因此不会将订单实体意外映射到客户DTO等。自包含代码 -没有运行时依赖项如果发生以下情况,则在构建时清除错误报告:映射不完整(并非所有目标属性都被映射)映射不正确(找不到正确的映射方法或类型转换)易于调试的映射代码(或手动编辑,例如在生成器中有错误的情况下)github.com/mapstruct/m…
  4. BeanCopier:BeanCopier是Cglib包中的一个类,用于对象的复制。目标对象必须先实例化 而且对象必须要有setter方法。

其实有很多种方法进行属性拷贝的,例如dozer等等 下面看下测试性能吧:以:万级进行测试,我觉得Cglib太给力了.可以在遇到属性拷贝瓶颈时考虑.当然他们各有优点哈,功能也不尽相同.还需要多使用体会. 输出结果:手动Copy >Mapstuct>= cglibCopy > springBeanUtils > apachePropertyUtils > apacheBeanUtils 可以理解为: 手工复制 > cglib > 反射 > Dozer。

测试结果评估:

数据量ApacheSpringMapStructBeanCopier
100w391ms250ms45ms57ms
10w82ms34ms8ms10ms
1w30ms19ms2ms7ms
1k15ms6ms1ms5ms
1005ms3ms1ms4ms
102ms1ms1ms4ms

根据测试结果,我们可以得出在速度方面,MapStruct是最好的,执行速度是 Apache BeanUtils 的10倍、Spring BeanUtils 的 4-5倍、和 BeanCopier 的速度差不多。 由此可以看出,在大数据量级的情况下,MapStruct 和 BeanCopier 都有着较高的性能优势,其中 MapStruct 尤为优秀。如果你仅是在日常处理少量的对象时,选取哪个其实变得并不重要,但数据量大时建议还是使用MapStruct 或 BeanCopier 的方式,提高接口性能。

暂时提供 BeanCopier 的工具类和 BeanUtils与 BeanCopier 测试结果

场景耗时(10000次调用)原理
BeanUtils232ms反射
BeanCopier116ms修改字节码
BeanCopier(加缓存)6ms修改字节码

BeanCopier 工具类

/**
 * @author chilei
 * @since 2023/6/19
 * @description BeanCopier是Cglib包中的一个类,用于对象的复制
 * copy(...)需要传入“源数据<S>”,“目标数据<T>”,没有返回值
 * copyTo(...)需要传入“源数据<S>”,“目标数据<T>”,返回“目标数据<T>”类型
 */
public class BeanCopierUtil {
    /**
     * BeanCopier的缓存
     */
    static final ConcurrentHashMap<String, BeanCopier> BEAN_COPIER_CACHE = new ConcurrentHashMap<>();

    /**
     * 生成key
     *
     * @param srcClazz 源文件的class
     * @param tgtClazz 目标文件的class
     * @return string
     */
    private static <S, T> String genKey(Class<S> srcClazz, Class<T> tgtClazz) {
        return srcClazz.getName() + tgtClazz.getName();
    }

    /**
     * BeanCopier的copy
     *
     * @param source 源文件的
     * @param target 目标文件
     */
    public static <S, T> void copy(S source, T target) {
        if (source == null || target == null) {
            throw new IllegalArgumentException("Source must not be null");
        }
        String key = genKey(source.getClass(), target.getClass());
        BeanCopier beanCopier;
        if (BEAN_COPIER_CACHE.containsKey(key)) {
            beanCopier = BEAN_COPIER_CACHE.get(key);
        } else {
            beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);
            BEAN_COPIER_CACHE.put(key, beanCopier);
        }
        beanCopier.copy(source, target, null);
    }

    /**
     * BeanCopier的copy
     *
     * @param source         源文件的
     * @param targetSupplier 目标文件
     * @param <T>            目标文件
     * @return 目标文件
     */
    public static <S, T> T copyTo(S source, Supplier<T> targetSupplier) {
        if (source == null || targetSupplier == null) {
            throw new IllegalArgumentException("Source must not be null");
        }
        T target = targetSupplier.get();
        copy(source, target);
        return target;
    }

    /**
     * 转换对象,有callback
     *
     * @param source         源对象
     * @param targetSupplier 目标对象供应方
     * @param callBack       回调方法
     * @param <S>            源对象类型
     * @param <T>            目标对象类型
     * @return 目标对象
     */
    public static <S, T> T copyTo(S source, Supplier<T> targetSupplier, ConvertCallback<S, T> callBack) {
        if (null == source || null == targetSupplier) {
            return null;
        }

        T target = targetSupplier.get();
        copy(source, target);
        if (callBack != null) {
            callBack.callback(source, target);
        }
        return target;
    }

    /**
     * @param sources        源对象list
     * @param targetSupplier 目标对象供应方
     * @param <S>            源对象类型
     * @param <T>            目标对象类型
     * @return 目标对象list
     */
    public static <S, T> List<T> copyListTo(List<S> sources, Supplier<T> targetSupplier) {
        return copyListTo(sources, targetSupplier, null);
    }

    /**
     * 转换对象
     *
     * @param sources        源对象list
     * @param targetSupplier 目标对象供应方
     * @param callBack       回调方法
     * @param <S>            源对象类型
     * @param <T>            目标对象类型
     * @return 目标对象list
     */
    public static <S, T> List<T> copyListTo(List<S> sources, Supplier<T> targetSupplier, ConvertCallback<S, T> callBack) {
        if (null == sources || null == targetSupplier) {
            return null;
        }

        List<T> list = new ArrayList<>(sources.size());
        for (S source : sources) {
            T target = targetSupplier.get();
            copy(source, target);
            if (callBack != null) {
                callBack.callback(source, target);
            }
            list.add(target);
        }
        return list;
    }

    /**
     * 转换回调接口
     *
     * @param <S> 源对象类型
     * @param <T> 目标对象类型
     */
    @FunctionalInterface
    public interface ConvertCallback<S, T> {
        void callback(S s, T t);
    }
}