大多时候时候使用的是
Apache或Spring BeanUtils,今天,我们来看一下一个更高效的属性拷贝方式:BeanCopier。
首先梳理出来现在有哪些对象拷贝的方式:
- Apache的BeanUtils:BeanUtils是Apache commens组件里面的成员,由Apache提供的一套开源api,用于简化对javaBean的操作,能够对基本类型自动转换。
- Spring的BeanUtils:BeanUtils是Spring框架提供的对Java反射和自省API的包装。其主要目的是利用反射机制对JavaBean的属性进行处理。
- Mapstruct:MapStruct是一个Java 注释处理器,用于为Java Bean类生成类型安全和高性能的映射器。它使您不必手工编写映射代码,这是一个繁琐且容易出错的任务。该生成器具有合理的默认值和许多内置的类型转换,但是在配置或实现特殊行为时,它会自动退出。与运行时工作的映射框架相比,MapStruct具有以下优点:通过使用普通方法调用而不是反射来快速执行编译时类型安全。只能映射彼此映射的对象和属性,因此不会将订单实体意外映射到客户DTO等。自包含代码 -没有运行时依赖项如果发生以下情况,则在构建时清除错误报告:映射不完整(并非所有目标属性都被映射)映射不正确(找不到正确的映射方法或类型转换)易于调试的映射代码(或手动编辑,例如在生成器中有错误的情况下)github.com/mapstruct/m…
- BeanCopier:BeanCopier是Cglib包中的一个类,用于对象的复制。目标对象必须先实例化 而且对象必须要有setter方法。
其实有很多种方法进行属性拷贝的,例如dozer等等 下面看下测试性能吧:以:万级进行测试,我觉得Cglib太给力了.可以在遇到属性拷贝瓶颈时考虑.当然他们各有优点哈,功能也不尽相同.还需要多使用体会. 输出结果:手动Copy >Mapstuct>= cglibCopy > springBeanUtils > apachePropertyUtils > apacheBeanUtils 可以理解为: 手工复制 > cglib > 反射 > Dozer。
测试结果评估:
| 数据量 | Apache | Spring | MapStruct | BeanCopier |
|---|---|---|---|---|
| 100w | 391ms | 250ms | 45ms | 57ms |
| 10w | 82ms | 34ms | 8ms | 10ms |
| 1w | 30ms | 19ms | 2ms | 7ms |
| 1k | 15ms | 6ms | 1ms | 5ms |
| 100 | 5ms | 3ms | 1ms | 4ms |
| 10 | 2ms | 1ms | 1ms | 4ms |
根据测试结果,我们可以得出在速度方面,MapStruct是最好的,执行速度是 Apache BeanUtils 的10倍、Spring BeanUtils 的 4-5倍、和 BeanCopier 的速度差不多。 由此可以看出,在大数据量级的情况下,MapStruct 和 BeanCopier 都有着较高的性能优势,其中 MapStruct 尤为优秀。如果你仅是在日常处理少量的对象时,选取哪个其实变得并不重要,但数据量大时建议还是使用MapStruct 或 BeanCopier 的方式,提高接口性能。
暂时提供 BeanCopier 的工具类和 BeanUtils与 BeanCopier 测试结果
| 场景 | 耗时(10000次调用) | 原理 |
|---|---|---|
| BeanUtils | 232ms | 反射 |
| BeanCopier | 116ms | 修改字节码 |
| 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);
}
}