-
Notifications
You must be signed in to change notification settings - Fork 7
对象拷贝用法及性能比较
在现在的很多业务系统中,基本上都会使用对象之间的拷贝操作,当属性非常多的时候,自然就需要一些工具来操作对象之间的拷贝操作。
- org.apache.commons.beanutils.BeanUtils#copyProperties
- org.apache.commons.beanutils.PropertyUtils#copyProperties
- org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object)
- org.springframework.cglib.beans.BeanCopier#copy
- Spring3.2以后把CGLib的jar包集成进Spring core包中了
- net.sf.cglib.beans.BeanCopier#copy
使用静态类调用,最终转化为两个单例的工具对象。默认的初始化BeanUtilsBean方法如下:
/**
* <p>Constructs an instance using new property
* and conversion instances.</p>
*/
public BeanUtilsBean() {
this(new ConvertUtilsBean(), new PropertyUtilsBean());
}
默认的获取BeanUtilsBean实例方法如下:
/**
* Contains <code>BeanUtilsBean</code> instances indexed by context classloader.
*/
private static final ContextClassLoaderLocal<BeanUtilsBean>
BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal<BeanUtilsBean>() {
// Creates the default instance used when the context classloader is unavailable
@Override
protected BeanUtilsBean initialValue() {
return new BeanUtilsBean();
}
};
/**
* Gets the instance which provides the functionality for {@link BeanUtils}.
* This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
* This mechanism provides isolation for web apps deployed in the same container.
*
* @return The (pseudo-singleton) BeanUtils bean instance
*/
public static BeanUtilsBean getInstance() {
return BEANS_BY_CLASSLOADER.get();
}
ConvertUtilsBean可以通过ConvertUtils全局自定义注册。
org.apache.commons.beanutils.ConvertUtilsBean#register(org.apache.commons.beanutils.Converter, java.lang.Class<?>)
PropertyUtilsBean的copyProperties方法实现了拷贝的算法。
- 动态bean:orig instanceof DynaBean:Object value = ((DynaBean)orig).get(name);然后把value复制到动态bean类
- Map类型:orig instanceof Map:key值逐个拷贝
- 其他普通类:从beanInfo【每一个对象都有一个缓存的bean信息,包含属性字段等】取出name,然后把sourceClass和targetClass逐个拷贝
- 即采用
PropertyDescriptor
类实现
- 即采用
也是采用PropertyDescriptor
类的实现,不过跟Apache版本有细微的差别。通过targetPd的writeMethod和sourcePd的readMethod进行操作,如果targetPd的writeMethod的第一个参数类型跟readMethod的返回值类型一致,则会进行复制操作。
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
copier = BeanCopier.create(source.getClass(), target.getClass(), false);
copier.copy(source, target, null);
Create对象过程:产生sourceClass-》TargetClass的拷贝代理类,放入jvm中,所以创建的代理类的时候比较耗时。最好保证这个对象的单例模式,可以参照本工程中的做法。Copy属性过程:调用生成的代理类,代理类的代码和手工操作的代码很类似,效率非常高。
创建过程:源码见net.sf.cglib.beans.BeanCopier.Generator.generateClass(ClassVisitor)
- 获取sourceClass的所有public get 方法-》PropertyDescriptor[] getters
- 获取TargetClass 的所有 public set 方法-》PropertyDescriptor[] setters
- 遍历setters的每一个属性,执行4和5
- 按setters的name生成sourceClass的所有setter方法-》PropertyDescriptor getter【不符合javabean规范的类将会可能出现空指针异常】
- PropertyDescriptor[] setters-》PropertyDescriptor setter
- 将setter和getter名字和类型 配对,生成代理类的拷贝方法。
https://blog.csdn.net/jianhua0902/article/details/8155368
功能说明 | Apache- PropertyUtils | Apache- BeanUtils | Spring- BeanUtils | Cglib-BeanCopier |
---|---|---|---|---|
是否可以扩展useConvete功能 | NO | YES | YES | YES(难用) |
(sourceObject,targetObject)的顺序 | 逆序 | 逆序 | 顺序 | 顺序 |
对sourceObject特殊属性的限制:(Date,BigDecimal等) | OK | OK | OK | OK |
相同属性名,且类型不匹配时候的处理 | 异常 | OK,并能进行初级转换,Long和Integer互转 | 拷贝部分属性 | OK,但是该属性不拷贝 |
Get和set方法不匹配的处理 | OK | OK | OK | OK |
通过实际试验(1000000次对象拷贝操作),得出以下结论(实际代码请参看github),
- CGLib BeanCopier花费时间:1111ms
- Spring BeanUtils花费时间:1462ms
- Apache BeanUtils花费时间:4109ms
- Apache PropertyUtils花费时间:2833ms
由试验也得出CGLib具备高性能,效率高的优点。次之为Spring BeanUtils。
SegmentFault: https://segmentfault.com/u/landy8530
简书:https://www.jianshu.com/u/36a7d3a994ac
CSDN:https://blog.csdn.net/landy8530
开源中国:https://my.oschina.net/landy8530
微信公众号:蚂蚁与咖啡的故事
Core组件
DataCache组件
Nosql组件
Export组件
文件服务组件
操作指引