曾有网友详细比对过各种属性copy工具(Apache BeanUtils、PropertyUtils, Spring BeanUtils, Cglib BeanCopier),Cglib的BeanCopier表现可谓相当优秀,原博文:Bean复制的几种框架性能比较。
但其直接使用却稍显繁琐,所以这里做了一些简单封装,以供参考,废话少说,show code:
package com.cafeboy.common.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.cglib.core.Converter;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* Bean转换器
*/
public class BeanConverter {
private static final Logger logger = LoggerFactory.getLogger(BeanConverter.class);
private static final ConcurrentHashMap<String, BeanCopier> copierMap = new ConcurrentHashMap<>();
/**
* BeanCopier.create()的过程相对比较耗时,所以尽量避免多次初始化
*/
private static <S, T> BeanCopier getTheBeanCopier(S source, T target, boolean ignoreNull) {
String key = source.getClass().getName() + "_" + target.getClass().getName() + "_" + ignoreNull;
BeanCopier copier = copierMap.get(key);
if (null == copier) {
synchronized (copierMap) {
copier = BeanCopier.create(source.getClass(), target.getClass(), ignoreNull);
copierMap.put(key, copier);
}
}
return copier;
}
public static <S, T> void convert(S source, T target) {
convert(source, target, true);
}
/**
* 批量转换
*
* @param sourceList 源bean列表
* @param targetClass 生成的目标list class, 要求bean有无参构造函数
* @return target bean list
*/
public static <S, T> List<T> convertList(List<S> sourceList, Class<T> targetClass) {
if (CollectionUtils.isEmpty(sourceList)) {
return Collections.emptyList();
}
List<T> targetList = new ArrayList<>(sourceList.size());
for (S source : sourceList) {
T target = convert(source, targetClass);
if (target != null) {
targetList.add(target);
}
}
return targetList;
}
/**
* 将原对象转换为目标类型的对象
*
* @param source 源对象
* @param targetClass 目标对象类型
* @return 目标对象
*/
public static <S, T> T convert(S source, Class<T> targetClass) {
T target = null;
try {
target = targetClass.newInstance();
convert(source, target, false);
} catch (InstantiationException | IllegalAccessException e) {
logger.error("bean convert error!", e);
}
return target;
}
/**
* bean属性copy
*
* @param source 源对象
* @param target 目标对象
* @param ignoreNull 是否忽略源对象的null属性,如果忽略,则保留目标对象属性值
*/
public static <S, T> void convert(S source, T target, boolean ignoreNull) {
BeanCopier copier = getTheBeanCopier(source, target, ignoreNull);
// 如果不忽略null值,则不使用Converter
if (!ignoreNull) {
copier.copy(source, target, null);
return;
}
// 忽略null值,自定义Converter
Converter converter = (srcValue, targetClass, context) -> {
// 如果忽略null值,则取目标对象值
if (srcValue == null) {
String property = getPropertyName(targetClass, String.valueOf(context));
return BeanMap.create(target).get(property);
}
// 如果类型不一致,也取目标对象值
if (!srcValue.getClass().isAssignableFrom(targetClass)) {
String property = getPropertyName(targetClass, String.valueOf(context));
return BeanMap.create(target).get(property);
}
// 否则取源对象值
return srcValue;
};
copier.copy(source, target, converter);
}
/**
* 返回属性名,setAge ---> age
*
* @param target 目标对象属性类型
* @param methodName 属性对应方法名
* @return 属性名
*/
private static String getPropertyName(Class target, String methodName) {
// 方法前缀长度,默认为3,即'get'的长度
int prefixLength = 3;
// 如果是布尔类型,去掉is
if (Boolean.class.isAssignableFrom(target) && methodName.startsWith("is")) {
prefixLength = 2;
}
// 截取,并将首字母转小写,最终获取属性名
int fieldNameLength = methodName.length() - prefixLength;
char[] newChar = new char[fieldNameLength];
System.arraycopy(methodName.toCharArray(), prefixLength, newChar, 0, fieldNameLength);
newChar[0] = Character.toLowerCase(newChar[0]);
return String.valueOf(newChar);
}
}
主要封装点如下:
- BeanCopier初始化相对耗时,这里做了缓存,避免多次初始化;
- 自定义Converter,可以忽略null值,以及处理字段类型不一致的问题;