更快的BeanUtil(对比BeanUtils,Cglib)

基于缓存和反射的高效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);

注意:

  1. 如果子类中有和父类同名的字段,将会优先使用子类的字段对象进行操作,而不会对父类同名字段对象做操作
  2. 如果source类与targe类中,同名字段的类型不一致,将会抛出RuntimeException
  3. 显而易见的,只有同名同类型的字段才会复制成功
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值