当前位置: 首页 > 工具软件 > ReflectASM > 使用案例 >

使用reflectasm实现bean快速复制

壤驷高旻
2023-12-01

BeanUtils简介:

BeanUtils提供对 Java反射和自省API的包装。其主要目的是利用反射机制对JavaBean的属性进行处理。我们知道,一个JavaBean通常包含了大量的属性,很 多情况下,对JavaBean的处理导致大量get/set代码堆积,增加了代码长度和阅读代码的难度。

谨记:

BeanUtils.copyProperties只对bean属性进行复制,这里的复制属于浅复制。且不能复制集合和数组。BeanUtils.copyProperties利用反射,直接将对象的引用set进去,并不是深拷贝。

在项目中所需要的业务非常多,所以我们的业务数据也会有很多种,这个时候就会有什么VO,DTO,PO等等这些,把业务和我们的基础数据进行分离转换。但是一直都没有什么好一点的转换类。后来用了一下BeanUtils.copyPropertie,和PropertyUtils.copyProperties()的方法,发现其效率非常低。这里也简单总结了一下他们的用法及原理以及自己实现的转换类;

用法:

BeanUtils.copyProperties("转换后的类", "要转换的类");
PropertyUtils.copyProperties("转换后的类", "要转换的类");

用法其实很简单,第一个参数是转换后的类,第二个参数是待转换的类;我们可以理解成为后转前;
原理:

其原理是通过JDK自带的反射机制动态的去get,set,从而去转换我们的类。但是要注意一点他们所支持的数据类型,还有一个就是假如一个类里面又写了一个类,例如这种:

public class Name{
 
}
class Name1{
 
}

一般叫做内部类,像这种类进行转换的时候,是不会成功的。因为在里面进行读写校验的时候不会通过;

PropertyDescriptor[] origDescriptors =
                getPropertyDescriptors(orig);
            for (int i = 0; i < origDescriptors.length; i++) {
                String name = origDescriptors[i].getName();
                if (isReadable(orig, name) && isWriteable(dest, name)) {
                    try {
                        Object value = getSimpleProperty(orig, name);
                        if (dest instanceof DynaBean) {
                            ((DynaBean) dest).set(name, value);
                        } else {
                                setSimpleProperty(dest, name, value);
                        }
                    } catch (NoSuchMethodException e) {
                        if (log.isDebugEnabled()) {
                            log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
                        }
                    }
                }
            }

上图是JDK的源代码,我们执行isRead和isWrite的时候并不会通过;

注意差异,

主要的区别在于BeanUtils
提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,而前者不支持这个功能,但是速度会更快一些。BeanUtils支持的转换类型如下:

* java.lang.BigDecimal
* java.lang.BigInteger
* boolean and java.lang.Boolean
* byte and java.lang.Byte
* char and java.lang.Character
* java.lang.Class
* double and java.lang.Double
* float and java.lang.Float
* int and java.lang.Integer
* long and java.lang.Long
* short and java.lang.Short
* java.lang.String
* java.sql.Date
* java.sql.Time
* java.sql.Timestamp 

阅读其源码,发现其内部是使用了装饰者模式,我发现Java得工具类很喜欢使用这种模式,而且也十分好用;
他们都使用到了BeanutilsBean和PropertyUtilsBean只不过BeanUtils多了一个转换的功能而已,但是性能上要比

PropertyUtils慢一些,其实两个都很慢,最好不要使用;

重写反射转换:

/**
	 * @param obj 转换的对象值
	 * @param clz 类对象
	 * @return 转换后的对象
	 */
	public static<T> T transferObject(Object obj,Class clz){
		T result = null;
		if(obj!=null&&!obj.equals("")){
		Method[] methods =  obj.getClass().getMethods();
		try {
			result = (T)clz.newInstance();
		} catch (Exception e1) {
			return null;
		}
		Method m;
		for(int i=0;i<methods.length;i++){
			m = methods[i];
			try {
				if(m.getName().startsWith("set")){
					String fieldName =  m.getName().replaceFirst("set", "");
					Method method = result.getClass().getMethod(m.getName(), m.getParameterTypes());
					Method getMethod = obj.getClass().getMethod("get"+fieldName, new Class[]{});
					method.invoke(result, getMethod.invoke(obj, new Object[]{}));
				}
			} catch (Exception e) {
				continue;
			}
		}
		}
		return result;
	}
上面这个方法也是用了Java反射去写的,但是少了很多校验以及转换。
所以在100万条数据的时候,效率是3739毫秒,而使用BeanUtils是5000毫秒左右。两个效率都不高;

ReflectASM,高性能的反射:

什么是ReflectASM ReflectASM是一个很小的java类库,主要是通过asm生产类来实现java反射,执行速度非常快,看了网上很多和反射的对比,觉得ReflectASM比较神奇,很想知道其原理,下面介绍下如何使用及原理;

		<dependency>
            <groupId>com.esotericsoftware</groupId>
            <artifactId>reflectasm</artifactId>
            <version>1.11.0</version>
        </dependency>
public static void main(String[] args) {  
        User user = new User();  
        //使用reflectasm生产User访问类  
        MethodAccess access = MethodAccess.get(User.class);  
        //invoke setName方法name值  
        access.invoke(user, "setName", "张三");  
        //invoke getName方法 获得值  
        String name = (String)access.invoke(user, "getName", null);  
        System.out.println(name);  
    }  

原理

上面代码的确实现反射的功能,代码主要的核心是 MethodAccess.get(User.class);
看了下源码,这段代码主要是通过asm生产一个User的处理类 UserMethodAccess(这个类主要是实现了invoke方法)的ByteCode,然后获得该对象,通过上面的invoke操作user类。

ASM反射转换:

private static Map<Class, MethodAccess> methodMap = new HashMap<Class, MethodAccess>();
 
	private static Map<String, Integer> methodIndexMap = new HashMap<String, Integer>();
 
	private static Map<Class, List<String>> fieldMap = new HashMap<Class, List<String>>();
 
	public static void copyProperties(Object desc, Object orgi) {
		MethodAccess descMethodAccess = methodMap.get(desc.getClass());
		if (descMethodAccess == null) {
			descMethodAccess = cache(desc);
		}
		MethodAccess orgiMethodAccess = methodMap.get(orgi.getClass());
		if (orgiMethodAccess == null) {
			orgiMethodAccess = cache(orgi);
		}
 
		List<String> fieldList = fieldMap.get(orgi.getClass());
		for (String field : fieldList) {
			String getKey = orgi.getClass().getName() + "." + "get" + field;
			String setkey = desc.getClass().getName() + "." + "set" + field;
			Integer setIndex = methodIndexMap.get(setkey);
			if (setIndex != null) {
				int getIndex = methodIndexMap.get(getKey);
				// 参数一需要反射的对象
				// 参数二class.getDeclaredMethods 对应方法的index
				// 参数对三象集合
				descMethodAccess.invoke(desc, setIndex.intValue(),
						orgiMethodAccess.invoke(orgi, getIndex));
			}
		}
	}
 
	// 单例模式
	private static MethodAccess cache(Object orgi) {
		synchronized (orgi.getClass()) {
			MethodAccess methodAccess = MethodAccess.get(orgi.getClass());
			Field[] fields = orgi.getClass().getDeclaredFields();
			List<String> fieldList = new ArrayList<String>(fields.length);
			for (Field field : fields) {
				if (Modifier.isPrivate(field.getModifiers())
						&& !Modifier.isStatic(field.getModifiers())) { // 是否是私有的,是否是静态的
					// 非公共私有变量
					String fieldName = StringUtils.capitalize(field.getName()); // 获取属性名称
					int getIndex = methodAccess.getIndex("get" + fieldName); // 获取get方法的下标
					int setIndex = methodAccess.getIndex("set" + fieldName); // 获取set方法的下标
					methodIndexMap.put(orgi.getClass().getName() + "." + "get"
							+ fieldName, getIndex); // 将类名get方法名,方法下标注册到map中
					methodIndexMap.put(orgi.getClass().getName() + "." + "set"
							+ fieldName, setIndex); // 将类名set方法名,方法下标注册到map中
					fieldList.add(fieldName); // 将属性名称放入集合里
				}
			}
			fieldMap.put(orgi.getClass(), fieldList); // 将类名,属性名称注册到map中
			methodMap.put(orgi.getClass(), methodAccess);
			return methodAccess;
		}
	}

执行1000000条效率80几毫秒,效率已经没问题了;

转载:https://blog.csdn.net/liaodehong/article/details/50379351

 类似资料: