基于缓存和反射的高效BeanUtil
BeanUtil算是个高频使用工具类了,很多地方都用得到,大家常用的BeanUtil大概有Spring的BeanUtils以及cglib的BeanCopier,前者使用反射进行getter、setter方法调用,后者使用CGLIB代理直接操作字节码调用getter、setter方法。
BeanCopier的性能要高出Spring的BeanUtils很多,我实测大概有5倍多的差距,但是BeanCopier用起来也更加繁琐一些,需要先传入source类和target类创建一个beanCopier实例,相比Spring的BeanUtils要多一个步骤,当然封装一下之后用起来都一样方便,问题是,这两个BeanUtil实际上都有个问题,强依赖于Getter、Setter方法,如果Bean没有Getter、Setter方法,或者重写了Getter、Setter方法,这两个工具类都会失效。
反射之所以慢,主要在于使用反射获取类的Method对象、Field对象时候慢,所以加快基于反射的Beanutil的一个简单思路,就是把需要用反射来获取的对象缓存起来,下次要用的时候就不用再一次获取了,效率会快很多,
通过起100个线程,每个线程跑100万次,测试发现,多线程环境下BeanCopier性能衰减的厉害,比单线程测试时候效率低了很多,实测我手写的BeanUtil在多线程环境下性能达到了BeanCopier的1.5倍左右,当然这是未封装过的BeanCopier的性能,用起来很麻烦,一旦封装性能差距会更大。
package com.tsd.dlmPlatform_common.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* @author HeJun
* @date 2020年9月28日
* @Version V1.4
* @Description JavaBean工具类,通过反射结果缓存机制,可以实现source类对象与target类对象间,同名字段值的高效率、线程安全复制
*
*/
@SuppressWarnings("unchecked")
public class HjBeanUtil {
// 构造器对象的key值
private static final String CONSTRUCT_KEY = "CONSTRUCT_KEY";
// 字段对象list的key值
private static final String FIELDS_LIST_KEY = "FIELDS_LIST_KEY";
// 创建反射元素缓存池单例对象,饿汉式
private volatile static CachePool CACHE_POOL = new CachePool();
/**
*
* @author HeJun
* @date 2020年9月28日
* @Version V1.3
* @Description 将source对象的同名字段值复制给targetClazz类的实例并返回
* @param <T>
* @param source
* @param targetClazz
* @return
* @throws Exception
*/
public static <T> T copy(Object source, Class<T> targetClazz, String...ignoreFields) {
T target = null;
// 获取source的类对象
if(source != null) {
Class<?> sourceClazz = source.getClass();
try {
// 如果缓存池中没有source的缓存就添加,并返回source缓存
ClazzCache sourceCache = getCacheFromPool(sourceClazz);
// 实例化target类对象
target = getInstance(targetClazz);
// 获得target类的字段List
List<Field> targetFieldsList = getFieldsList(targetClazz);
// 遍历targetFieldsList中所有的字段
for (Field targetField : targetFieldsList) {
if(ignoreFieldsInvoke(targetField, ignoreFields)) {
continue;
}
// 通过target字段的名字,从sourceCache缓存中获得source的字段(target字段需为source字段的子集)
Field sourceField = (Field) sourceCache.get(targetField.getName());
if (sourceField != null) {
Object value = sourceField.get(source);
targetField.set(target, value);
}
}
} catch (Exception e) {
throw new RuntimeException(
sourceClazz.getName() + " 转 " + targetClazz.getName() + " 失败\n" + e.getMessage(),e);
}
}
return target;
}
/**
*
* @author HeJun
* @date 2020年9月28日
* @Version V1.3
* @Description 将source对象的同名字段值复制给targetClazz类的实例并返回
* @param <T>
* @param source
* @param targetClazz
* @return
* @throws Exception
*/
public static <T> T copy(Object source, Class<T> targetClazz) {
return copy(source, targetClazz, null);
}
/**
*
* @author HeJun
* @date 2020年9月28日
* @Version V1.0
* @Description 将source对象的同名字段值复制给target对象
* @param source
* @param target
*/
public static void copy(Object source, Object target, String...ignoreFields) {
if(source != null) {
// 获取类对象
Class<?> sourceClazz = source.getClass();
Class<?> targetClazz = target.getClass();
try {
// 如果缓存池中没有source的缓存就添加,并返回source缓存
ClazzCache sourceCache = getCacheFromPool(sourceClazz);
// 获得target类的字段List
List<Field> targetFieldsList = getFieldsList(targetClazz);
// 遍历targetFieldsList中所有的字段
for (Field targetField : targetFieldsList) {
if(ignoreFieldsInvoke(targetField, ignoreFields)) {
continue;
}
// 通过target字段的名字,从sourceCache缓存中获得source的字段(target字段需为source字段的子集)
Field sourceField = (Field) sourceCache.get(targetField.getName());
if (sourceField != null) {
Object value = sourceField.get(source);
targetField.set(target, value);
}
}
} catch (Exception e) {
throw new RuntimeException(
sourceClazz.getName() + " 转 " + targetClazz.getName() + " 失败\n" + e.getMessage(),e);
}
}
}
/**
*
* @author HeJun
* @date 2020年9月28日
* @Version V1.0
* @Description 将source对象的同名字段值复制给target对象
* @param source
* @param target
*
*/
public static void copy(Object source, Object target) {
copy(source, target, null);
}
private static boolean ignoreFieldsInvoke(Field targetField,String...ignoreFields) {
boolean flag = false;
if(ignoreFields != null && ignoreFields.length > 0) {
String name = targetField.getName();
for (int i = 0; i < ignoreFields.length; i++) {
if(name.equals(ignoreFields[i])) {
flag = true;
break;
}
}
}
return flag;
}
/**
*
* @author HeJun
* @date 2021年4月17日
* @Version V1.0
* @Description 将sourceList内的元素对象的同名字段值复制给targetList
* @param <T>
* @param sourceList
* @param targetClazz
* @return
*/
public static <T> List<T> copyList(List<?> sourceList, Class<T> targetClazz, String...ignoreFields) {
List<T> targetList = null;
if(sourceList != null) {
targetList = new ArrayList<T>();
for (Object source : sourceList) {
targetList.add(copy(source, targetClazz,ignoreFields));
}
}
return targetList;
}
/**
*
* @author HeJun
* @date 2021年4月17日
* @Version V1.0
* @Description 将sourceList内的元素对象的同名字段值复制给targetList
* @param <T>
* @param sourceList
* @param targetClazz
* @return
*/
public static <T> List<T> copyList(List<?> sourceList, Class<T> targetClazz) {
return copyList(sourceList, targetClazz, null);
}
/**
*
* @author HeJun
* @date 2020年9月29日
* @Version V1.0
* @Description 将map转换成targetClazz类的实例
* @param <T>
* @param sourceMap
* @param targetClazz
* @return
*/
public static <T> T mapToObect(Map<String, ?> sourceMap, Class<T> targetClazz, String...ignoreFields) {
T target = null;
if(!HjBeanUtil.isEmpty(sourceMap)) {
try {
// 实例化target类对象
target = getInstance(targetClazz);
// 获得target类的字段List
List<Field> targetFieldsList = getFieldsList(targetClazz);
// 遍历targetFieldsList中所有的字段
for (Field targetField : targetFieldsList) {
if(ignoreFieldsInvoke(targetField, ignoreFields)) {
continue;
}
// 通过target字段的名字,从sourceCache缓存中获得source的字段(target字段需为source字段的子集)
Object value = sourceMap.get(targetField.getName());
targetField.set(target, value);
}
} catch (Exception e) {
throw new RuntimeException("sourceMap" + " 转 " + targetClazz.getName() + " 失败\n" + e.getMessage(),e);
}
}
return target;
}
/**
*
* @author HeJun
* @date 2020年9月29日
* @Version V1.0
* @Description source对象转有序map
* @param source
* @return
*/
public static LinkedHashMap<String, Object> objectToMap(Object source, String...ignoreFields) {
LinkedHashMap<String, Object> targetMap = null;
if(source != null) {
targetMap = new LinkedHashMap<>();
// 获取类对象
Class<?> sourceClazz = source.getClass();
try {
// 获得source类的字段List
List<Field> sourceFieldsList = getFieldsList(sourceClazz);
// 遍历sourceFieldsList中所有的字段
for (Field sourceField : sourceFieldsList) {
Object value = sourceField.get(source);
if(!ignoreFieldsInvoke(sourceField, ignoreFields) && value != null) {
targetMap.put(sourceField.getName(), value);
}
}
} catch (Exception e) {
throw new RuntimeException(sourceClazz.getName() + " 转 " + "targetMap" + " 失败\n" + e.getMessage(),e);
}
}
return targetMap;
}
/**
*
* @author HeJun
* @date 2020年9月27日
* @Version V1.2
* @Description 向缓存池添加反射元素缓存
* @param clazz
* @return
* @throws Exception
*/
private static ClazzCache getCacheFromPool(Class<?> clazz) throws Exception {
ClazzCache clazzCache = CACHE_POOL.get(clazz);
// 如果缓存中没有clazz的缓存就添加
if (clazzCache == null) {
clazzCache = addClazzCache(clazz);
}
return clazzCache;
}
/**
* @author HeJun
* @date 2020年9月27日
* @Version V1.0
* @Description 向缓存池添加ClazzCache缓存,并返回ClazzCache对象
* @param clazz
* @return
* @throws NoSuchMethodException
* @throws SecurityException
*/
private static ClazzCache addClazzCache(Class<?> clazz)
throws NoSuchMethodException, SecurityException {
ClazzCache clazzCache = CACHE_POOL.get(clazz);
if (clazzCache == null) {
clazzCache = new ClazzCache();
List<Field> fieldsList = new ArrayList<>();
Set<String> fieldNamesSet = new HashSet<>();
for (Class<?> currentClazz = clazz; currentClazz != Object.class; currentClazz = currentClazz
.getSuperclass()) {
// 获得当前clazz的字段数组
Field[] fields = currentClazz.getDeclaredFields();
for (Field field : fields) {
// 判断是否是final修饰的字段,如果是就不添加进clazzFields
if (!Modifier.isFinal(field.getModifiers())) {
field.setAccessible(true);
// 判断字段名是否重复,如果重复就不添加
if (fieldNamesSet.add(field.getName())) {
fieldsList.add(field);
clazzCache.put(field.getName(), field);
}
}
}
// 将类字段list放入clazzCache中
clazzCache.put(FIELDS_LIST_KEY, fieldsList);
// 将类的构造器放入clazzCache中
clazzCache.put(CONSTRUCT_KEY, clazz.getDeclaredConstructor());
}
// 将clazzCache反射元素缓存放入缓存池中
clazzCache = CACHE_POOL.putIfAbsent(clazz, clazzCache);
}
return clazzCache;
}
/**
*
* @author HeJun
* @date 2020年10月20日
* @Version V1.0
* @Description 根据类对象返回该类实例
* @param <T>
* @param clazz
* @return
* @throws Exception
*/
private static <T> T getInstance(Class<T> clazz) throws Exception {
ClazzCache cache = getCacheFromPool(clazz);
Constructor<T> constructor = (Constructor<T>) cache.get(CONSTRUCT_KEY);
return constructor.newInstance();
}
/**
*
* @author HeJun
* @date 2020年10月20日
* @Version V1.0
* @Description 从缓存池中获取clazz类的Field对象List
* @param clazz
* @return
* @throws Exception
*/
public static List<Field> getFieldsList(Class<?> clazz) throws Exception {
// 如果缓存池中没有source的缓存就添加,并返回source缓存
ClazzCache sourceCache = getCacheFromPool(clazz);
// 从sourceCache中获得sourceFieldsList
return (List<Field>) sourceCache.get(FIELDS_LIST_KEY);
}
/**
* 类字节码对象缓存类
*
* @author HeJun
*
*/
static class ClazzCache extends ConcurrentHashMap<String, Object> {
private static final long serialVersionUID = 688480073708628721L;
}
/**
* 类字节码对象缓存类的缓存池
*
* @author HeJun
*
*/
static class CachePool extends ConcurrentHashMap<Class<?>, ClazzCache> {
private static final long serialVersionUID = -6321066791324989547L;
}
public static boolean isEmpty(Collection<?> collection) {
if (collection == null || collection.size() == 0) {
return true;
}
return false;
}
/**
*
* @author HeJun
* @date 2020年8月13日
* @Version V1.0
* @Description str为null或者去掉字符串两端的空格后,与""比较是否为空字符串
* @param str
* @return
*/
public static boolean isEmpty(String str) {
if (str == null || "".equals(str.trim())) {
return true;
}
return false;
}
public static boolean isEmpty(Object[] array) {
if (array == null || array.length == 0) {
return true;
}
return false;
}
public static boolean isEmpty(Map<?, ?> map) {
if (map == null || map.size() == 0) {
return true;
}
return false;
}
使用方法:
TeacherDto teacherDto = new TeacherDto("老王","语文",47);
Teacher teacher = HjBeanUtil.copy(teacherDto,Teacher.class);
注意:
- 如果子类中有和父类同名的字段,将会优先使用子类的字段对象进行操作,而不会对父类同名字段对象做操作
- 如果source类与targe类中,同名字段的类型不一致,将会抛出RuntimeException
- 显而易见的,只有同名同类型的字段才会复制成功