关于beanUtils的copyproperties()方法,各大软件机构都有自己的源码,这里不在说他们的优缺点了。本次主要分享一个经过参考spring框架的beanUtils.copyProperties()方法,进行简化抽离的类复制方法。
spring框架的beanUtils.copyProperties()方法,之所以快,是因为加入了缓存机制以及使用了java提供的(set和get方法专用反射反省类-propertyDescriptor)。关于这个类机制,我也不知道,不顾既然java提供了那就用就完事了。
直接附上代码—代码过于简单,就不再讲述原理。
package com.xilidata.products.cocc.mtds.util;
import com.xilidata.core.foundation.exceptions.DataValidateException;
import com.xilidata.core.foundation.utils.ValidationUtil;
import com.xilidata.products.cocc.mtds.model.mtds.ItemBillModel;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
public class BeanUtil {
private static final Logger logger = LoggerFactory.getLogger(BeanUtil.class);
private static boolean defaultReplace = false;
private static boolean defaultCache = true;
// private static final ConcurrentHashMap<String, ConcurrentHashMap<String, CacheMethod>> cacheMap = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, ConcurrentHashMap<String, CacheMethodV2>> cacheMapV2 = new ConcurrentHashMap<>();
public static <T> T copyProperties(Object source, Class<T> tClass) throws DataValidateException {
return copyProperties(source, tClass, null, null, defaultReplace, defaultCache);
}
public static <T> T copyProperties(Object source, Class<T> tClass, List<Class<?>> ignoreClazzs) throws DataValidateException {
return copyProperties(source, tClass, ignoreClazzs, null, defaultReplace, defaultCache);
}
public static <T> T copyProperties(Object source, Class<T> tClass, List<Class<?>> ignoreClazzs,
List<String> ignoreFields) throws DataValidateException {
return copyProperties(source, tClass, ignoreClazzs, ignoreFields, defaultReplace, defaultCache);
}
public static <T> T copyProperties(Object source, Class<T> tClass, List<Class<?>> ignoreClazzs,
List<String> ignoreFields, boolean replaceAll) throws DataValidateException {
return copyProperties(source, tClass, ignoreClazzs, ignoreFields, replaceAll, defaultCache);
}
public static <T> T copyProperties(Object source, Class<T> tClass, List<Class<?>> ignoreClazzs,
List<String> ignoreFields, boolean replaceAll, boolean openCache) throws DataValidateException {
try {
Constructor<T> constructor = tClass.getConstructor();
return copyProperties(source, constructor.newInstance(), ignoreClazzs, ignoreFields, replaceAll, openCache);
} catch (NoSuchMethodException e) {
// 找不到构造方法
throw new DataValidateException("目标类校验:没有对应的无参构造");
} catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
throw new DataValidateException("目标类校验:构造执行异常");
}
}
public static <T> T copyProperties(Object source, T target) throws DataValidateException {
return copyProperties(source, target, null, null, defaultReplace, defaultCache);
}
public static <T> T copyProperties(Object source, T target, List<Class<?>> ignoreClazzs) throws DataValidateException {
return copyProperties(source, target, ignoreClazzs, null, defaultReplace, defaultCache);
}
public static <T> T copyProperties(Object source, T target, List<Class<?>> ignoreClazzs, List<String> ignoreFields) throws DataValidateException {
return copyProperties(source, target, ignoreClazzs, ignoreFields, defaultReplace, defaultCache);
}
/**
* @param source 被复制的对象
* @param target 需要复制的对象
* @param ignoreClazzs 过滤不参与复制字段的class
* @param ignoreFields 过滤不参与复制的字段
* @param replaceAll 是否全部覆盖,默认false 为null不复制
* @param openCache 是否开始缓存 默认开启
* @throws DataValidateException
*/
public static <T> T copyProperties(Object source, T target, List<Class<?>> ignoreClazzs, List<String> ignoreFields, boolean replaceAll, boolean openCache) throws DataValidateException {
if (openCache) {
return copyPropertiesV3(source, target, ignoreClazzs, ignoreFields, replaceAll, true);
} else {
return copyPropertiesV1(source, target, ignoreClazzs, ignoreFields, replaceAll);
}
}
private static <T> T copyPropertiesV1(Object source, T target, List<Class<?>> ignoreClazzs, List<String> ignoreFields,
boolean replaceAll) throws DataValidateException {
if (source == null || target == null) {
throw new DataValidateException("参数对象不可为空");
}
Class<?> sourceClass = source.getClass();
Class<?> targetClass = target.getClass();
Class<?> tempClass = sourceClass;
// 找到所有的父级包含的字段
Map<String, Class<?>> filedMap = new HashMap<>();
out:
do {
if (!ValidationUtil.isEmpty(ignoreClazzs)) {
for (Class<?> ignoreClazz : ignoreClazzs) {
if (tempClass.equals(ignoreClazz)) {
continue out;
}
}
}
inner:
for (Field filed : tempClass.getDeclaredFields()) {
if (!ValidationUtil.isEmpty(ignoreFields)) {
for (String ignoreField : ignoreFields) {
if (ignoreField.equals(filed.getName())) {
continue inner;
}
}
}
filedMap.put(filed.getName(), filed.getType());
}
} while (!Object.class.equals(tempClass = tempClass.getSuperclass()));
Set<Map.Entry<String, Class<?>>> entries = filedMap.entrySet();
for (Map.Entry<String, Class<?>> entry : entries) {
try {
Method getMethod = sourceClass.getMethod(getterStr(entry.getKey()));
Object value = getMethod.invoke(source);
if (replaceAll || null != value) {
Method setMethod = targetClass.getMethod(setterStr(entry.getKey()), entry.getValue());
setMethod.invoke(target, value);
}
} catch (InvocationTargetException e) {
// 方法调用失败
// e.printStackTrace();
// logger.warn("字段:{} 异常 方法调用失败", entry.getKey());
} catch (IllegalAccessException e) {
// 方法不可访问
// e.printStackTrace();
// logger.warn("字段:{} 异常 方法不可访问", entry.getKey());
} catch (NoSuchMethodException e) {
// 找不到set、get方法
// e.printStackTrace();
// logger.warn("字段:{} 异常 找不到set、get方法", entry.getKey());
}
}
return target;
}
private static <T> T copyPropertiesV3(Object source, T target, List<Class<?>> ignoreClazzs, List<String> ignoreFields,
boolean replaceAll, boolean openCache) throws DataValidateException {
return copyPropertiesV3(source, target, ignoreClazzs, ignoreFields, replaceAll, openCache, null, null, false);
}
public static <T1, T> List<T> copyPropertiesBatch(List<T1> sources, Class<T> targetClass) throws DataValidateException {
return copyPropertiesBatch(sources, targetClass, null, null, defaultReplace, defaultCache);
}
public static <T1, T> List<T> copyPropertiesBatch(List<T1> sources, Class<T> targetClass,
List<Class<?>> ignoreClazzs) throws DataValidateException {
return copyPropertiesBatch(sources, targetClass, ignoreClazzs, null, defaultReplace, defaultCache);
}
public static <T1, T> List<T> copyPropertiesBatch(List<T1> sources, Class<T> targetClass, List<Class<?>> ignoreClazzs,
List<String> ignoreFields) throws DataValidateException {
return copyPropertiesBatch(sources, targetClass, ignoreClazzs, ignoreFields, defaultReplace, defaultCache);
}
public static <T1, T> List<T> copyPropertiesBatch(List<T1> sources, Class<T> targetClass, List<Class<?>>
ignoreClazzs, List<String> ignoreFields,
boolean replaceAll) throws DataValidateException {
return copyPropertiesBatch(sources, targetClass, ignoreClazzs, ignoreFields, replaceAll, defaultCache);
}
public static <T1, T> List<T> copyPropertiesBatch(List<T1> sources, Class<T> targetClass, List<Class<?>>
ignoreClazzs, List<String> ignoreFields, boolean replaceAll, boolean openCache) throws DataValidateException {
if (null == sources || sources.size() == 0) {
return Collections.emptyList();
}
T1 t1 = sources.get(0);
Class<?> aClass = t1.getClass();
ConcurrentHashMap<String, CacheMethodV2> getConcurrentHashMap = cacheMapV2.get(aClass.getName());
if (ValidationUtil.isEmpty(getConcurrentHashMap)) {
getConcurrentHashMap = cacheMapV2.put(aClass.getName(), getAll(aClass));
}
ConcurrentHashMap<String, CacheMethodV2> setConcurrentHashMap;
if (aClass.getName().equals(targetClass.getName())) {
setConcurrentHashMap = getConcurrentHashMap;
} else {
setConcurrentHashMap = cacheMapV2.get(targetClass.getName());
if (ValidationUtil.isEmpty(setConcurrentHashMap)) {
setConcurrentHashMap = cacheMapV2.put(targetClass.getName(), getAll(targetClass));
}
}
List<T> list = new ArrayList<>();
T target;
Constructor<T> constructor;
try {
constructor = targetClass.getConstructor();
} catch (NoSuchMethodException e) {
throw new DataValidateException("target class can not be instance");
}
try {
for (T1 t11 : sources) {
target = constructor.newInstance();
copyPropertiesV3(t11, target, ignoreClazzs, ignoreFields, replaceAll, openCache, getConcurrentHashMap, setConcurrentHashMap, true);
list.add(target);
}
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new DataValidateException("target class can not be instance");
}
return list;
}
public static <T> Map<String, T> mapToMap(Map<String, T> sourceMap, Map<String, T> targetMap) {
return mapToMap(sourceMap, targetMap, null);
}
public static <T> Map<String, T> mapToMap(Map<String, T> sourceMap, Map<String, T> targetMap, List<String> fields) {
if (null == sourceMap) {
return targetMap;
}
if (null == targetMap) {
return null;
}
outer:
for (Map.Entry<String, T> entry : sourceMap.entrySet()) {
if (null != fields) {
for (String field : fields) {
if (Objects.equals(entry.getKey(), field)) {
continue outer;
}
}
}
targetMap.put(entry.getKey(), entry.getValue());
}
return targetMap;
}
public static <T> T mapToJavaObject(Map<String, Object> map, Class<T> clazz) {
return mapToJavaObject(map, clazz, null, null);
}
public static <T> T mapToJavaObject(Map<String, Object> map, Class<T> clazz, List<String>
ignoreFields, List<Class<?>> ignoreClazzs) {
try {
T t = clazz.getConstructor().newInstance();
return mapToJavaObject(map, t, ignoreFields, ignoreClazzs);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
return null;
}
}
public static <T> T mapToJavaObject(Map<String, Object> map, T t, List<String>
ignoreFields, List<Class<?>> ignoreClazzs) {
if (t == null) {
return null;
}
Class<?> clazz = t.getClass();
ConcurrentHashMap<String, CacheMethodV2> setMap = cacheMapV2.get(clazz.getName());
if (setMap == null) {
setMap = getAll(clazz);
cacheMapV2.put(clazz.getName(), setMap);
}
CacheMethodV2 methodV2;
Object value;
outer:
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (null != ignoreFields) {
for (String field : ignoreFields) {
if (Objects.equals(field, entry.getKey())) {
continue outer;
}
}
}
if (entry.getKey() == null) {
continue;
}
value = entry.getValue();
methodV2 = setMap.get(entry.getKey());
if (null == methodV2) {
continue;
}
if (null != ignoreClazzs) {
for (Class<?> aClass : ignoreClazzs) {
if (Objects.equals(aClass.getName(), methodV2.getClassName())) {
continue outer;
}
}
}
try {
if (value != null) {
methodV2.getWriteMethod().invoke(t, value);
}
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
return t;
}
private static <T> T copyPropertiesV3(Object source, T target, List<Class<?>>
ignoreClazzs, List<String> ignoreFields, boolean replaceAll, boolean openCache,
ConcurrentHashMap<String, CacheMethodV2> getConcurrentHashMap,
ConcurrentHashMap<String, CacheMethodV2> setConcurrentHashMap, boolean enableContinue) throws DataValidateException {
if (source == null || target == null) {
throw new DataValidateException("参数对象不可为空");
}
boolean isMap = target instanceof Map;
Class<?> sourceClass = source.getClass();
Class<?> targetClass = target.getClass();
if (ValidationUtil.isEmpty(getConcurrentHashMap)) {
if (ValidationUtil.isEmpty(getConcurrentHashMap = cacheMapV2.get(sourceClass.getName()))) {
getConcurrentHashMap = getAll(sourceClass);
if (openCache) {
cacheMapV2.put(sourceClass.getName(), getConcurrentHashMap);
}
}
}
if (sourceClass.equals(targetClass)) {
setConcurrentHashMap = getConcurrentHashMap;
} else if (ValidationUtil.isEmpty(setConcurrentHashMap)) {
if (ValidationUtil.isEmpty(setConcurrentHashMap = cacheMapV2.get(targetClass.getName()))) {
setConcurrentHashMap = getAll(targetClass);
if (openCache) {
cacheMapV2.put(targetClass.getName(), setConcurrentHashMap);
}
}
}
out:
for (Map.Entry<String, CacheMethodV2> entry : getConcurrentHashMap.entrySet()) {
if (!ValidationUtil.isEmpty(ignoreClazzs)) {
for (Class<?> ignoreClazz : ignoreClazzs) {
if (entry.getValue().getClassName().equals(ignoreClazz.getName())) {
continue out;
}
}
}
if (!ValidationUtil.isEmpty(ignoreFields)) {
for (String ignoreField : ignoreFields) {
if (ignoreField.equals(entry.getKey())) {
continue out;
}
}
}
try {
CacheMethodV2 cacheMethodV2 = setConcurrentHashMap.get(entry.getKey());
if (null == cacheMethodV2) {
continue;
}
Method setMethod = null;
Object value = entry.getValue().getReadMethod().invoke(source);
if (value instanceof List) {
List<?> list = (List<? extends Object>) value;
if (list.size() > 0) {
value = copyPropertiesBatch(list, list.get(0).getClass());
}
} else if (value instanceof BigDecimal) {
value = new BigDecimal(((BigDecimal) value).toPlainString());
} else if (value instanceof BigInteger) {
value = BigInteger.valueOf(((BigInteger) value).longValue());
} else if (value instanceof String) {
value = String.valueOf(value);
} else if (value != null && !isBaseType(value)) {
try {
Constructor<?> constructor = cacheMethodV2.getReadMethod().getReturnType().getConstructor();
Object o = constructor.newInstance();
copyProperties(value, o);
value = o;
} catch (NoSuchMethodException | InstantiationException e) {
continue;
}
} else if (value instanceof Map) {
if (isMap) {
value = mapToMap((Map<String, Object>) value, new HashMap<String, Object>());
} else {
value = mapToJavaObject((Map<String, Object>) value, cacheMethodV2.getReadMethod().getReturnType());
}
}
if (!enableContinue && replaceAll && null == value) {
setMethod = cacheMethodV2.getWMethod();
if (null == setMethod && cacheMethodV2.getEnableSet()) {
try {
setMethod = targetClass.getMethod(setterStr(entry.getKey()), cacheMethodV2.getWriteMethod().getParameterTypes()[0]);
} catch (NoSuchMethodException e) {
cacheMethodV2.setEnableSet(false);
continue;
}
cacheMethodV2.setWMethod(setMethod);
}
} else if (null != value) {
setMethod = cacheMethodV2.getWriteMethod();
}
if (null != setMethod) {
setMethod.invoke(target, value);
}
} catch (IllegalAccessException e) {
// e.printStackTrace();
// logger.warn("字段:{} 异常 方法不可访问", entry.getKey());
} catch (InvocationTargetException e) {
// e.printStackTrace();
// logger.warn("字段:{} 异常 set/get方法调用异常", entry.getKey());
}
}
return target;
}
private static boolean isBaseType(Object obj) {
if (null == obj) {
return false;
}
return isBaseType(obj.getClass());
}
private static boolean isBaseType(Class<?> clazz) {
// if (BigDecimal.class.equals(clazz)) {
// return true;
// }
if (clazz != null) {
try {
Field type = clazz.getField("TYPE");
return ((Class<?>) type.get(null)).isPrimitive();
} catch (NoSuchFieldException | IllegalAccessException e) {
return false;
}
}
return false;
}
private static ConcurrentHashMap<String, CacheMethodV2> getAll(Class<?> clazz) {
ConcurrentHashMap<String, CacheMethodV2> map = new ConcurrentHashMap<>(16 * 4 / 3 + 1);
Class<?> tempClazz = clazz;
do {
for (Field field : tempClazz.getDeclaredFields()) {
try {
map.put(field.getName(), new CacheMethodV2(field.getName(), tempClazz.getName(), tempClazz));
} catch (IntrospectionException e) {
// 反省异常
// e.printStackTrace();
// logger.warn("字段:{} 类:{} 反省异常", field.getName(), sourceClass);
}
}
} while (!Object.class.equals(tempClazz = tempClazz.getSuperclass()));
return map;
}
public static <K, V> V getOrSetDefault(AbstractMap<K, V> map, K k, V defaultV) {
return map.computeIfAbsent(k, k1 -> defaultV);
}
public static Method getWriteMethod(String propertyName, Class<?> beanClass) {
try {
PropertyDescriptor pd = new PropertyDescriptor(propertyName, beanClass);
return pd.getWriteMethod();
} catch (IntrospectionException e) {
}
return null;
}
public static Method getReadMethod(String propertyName, Class<?> beanClass) {
try {
PropertyDescriptor pd = new PropertyDescriptor(propertyName, beanClass);
return pd.getReadMethod();
} catch (IntrospectionException e) {
}
return null;
}
public static PropertyDescriptor getPropertyDescriptor(String propertyName, Class<?> beanClass) {
try {
PropertyDescriptor pd = new PropertyDescriptor(propertyName, beanClass);
return pd;
} catch (IntrospectionException e) {
}
return null;
}
public static String getterStr(String str) {
char[] chars = str.toCharArray();
if (chars[0] >= 97 && chars[0] <= 122) {
chars[0] -= 32;
}
return "get" + String.valueOf(chars);
}
public static String setterStr(String str) {
char[] chars = str.toCharArray();
if (chars[0] >= 97 && chars[0] <= 122) {
chars[0] -= 32;
}
return "set" + String.valueOf(chars);
}
public static void clearCache() {
cacheMapV2.clear();
}
@Getter
private static class CacheMethodV2 {
/**
* 字段名
*/
private final String name;
/**
* 属性解释器
*/
private final PropertyDescriptor pd;
/**
* 字段实际在那个类
*/
private final String className;
private Method WMethod;
private Method writeMethod;
private Method readMethod;
private boolean enableSet = true;
public CacheMethodV2(String name, PropertyDescriptor pd, String className) {
this.name = name;
this.pd = pd;
this.className = className;
}
public CacheMethodV2(String name, String className, Class<?> clazz) throws IntrospectionException {
this.name = name;
this.className = className;
this.pd = new PropertyDescriptor(name, clazz);
}
public final Method getReadMethod() {
if (null != readMethod) {
return readMethod;
}
return readMethod = pd.getReadMethod();
}
public final Method getWriteMethod() {
if (null != writeMethod) {
return writeMethod;
}
return writeMethod = pd.getWriteMethod();
}
public void setWMethod(Method m) {
this.WMethod = m;
}
public Method getWMethod() {
return this.WMethod;
}
public Method getWMethodOrDefault(Function<CacheMethodV2, Method> fun) {
return fun.apply(this);
}
public boolean getEnableSet() {
return enableSet;
}
public void setEnableSet(boolean enableSet) {
this.enableSet = enableSet;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CacheMethodV2)) return false;
CacheMethodV2 that = (CacheMethodV2) o;
return Objects.equals(name, that.name) && Objects.equals(className, that.className);
}
@Override
public int hashCode() {
return Objects.hash(name, className);
}
}
}