平时对象参数拷贝时,顺手使用的都是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();
}
}
在使用时,有两点需要注意的地方:
- target不能使用lombok等工具的链式调用,只有符合标准的set方法,cglib才会调用赋值。
- 只会拷贝source,target类型和名称完全一致的属性。