一、对象属性拷贝工具类
”天下武功,唯快不破“。在互联网行业中体现的更加淋淋尽致。我们在业务系统会经常遇到业务对象间属性的拷贝,对如外接口一般都使用特定的DTO对象,而不会使用领域模型,以避免两者的变动互相影响。我们不仅要关注“快”,还要注重CPU的稳定即避免CPU使用的大起大落现象。如何高效完成属性的拷贝并降低对CPU的使用率或避免CPU的抖动。
相关博文已经有很多,为什么还要自己在一篇类似的哪?原因有二:一是加深理解二是比较各自优劣。目前对象间属性的拷贝常用的方法大致如下:
- 手动拷贝(set)
- 动态代理
cglib版本:net.sf.cglib.beans.BeanCopier.copy(Object from, Object to, Converter converter)
- 反射机制
Spring版本:org.springframework.beans.BeanUtils.copyProperties(Object source, Object target)
Apache版本:org.apache.commons.beanutils.PropertyUtils.copyProperties(Object dest, Object orig)
org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)
DozerMapper
二、实践说明性能优劣
1、环境
WIN7 i5,12G内存,
JVM:
java version "1.8.0_51"
Java(TM) SE Runtime Environment (build 1.8.0_51-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)
依赖jar及版本
2、代码结构
package test;
/**
* @author wy
*
*/
public interface IMethodCallBack {
public String getMethodName();
public DestBean callBack(SourceBean sourceBean) throws Exception;
}
View Code
package test;
/**
*
* @author wy
*
*/
public class CopyProcessor {
public int count;
public CopyProcessor(int count) {
this.count = count;
System.out.println("性能测试=========" + this.count + "=========");
}
public void processor(IMethodCallBack methodCallBack, SourceBean sourceBean) throws Exception {
long begin = System.currentTimeMillis();
DestBean destBean = null;
System.out.println(methodCallBack.getMethodName() + "开始进行测试");
for (int i = 0; i < count; i++) {
destBean = methodCallBack.callBack(sourceBean);
}
long end = System.currentTimeMillis();
System.out.println(methodCallBack.getMethodName() + " 耗时 = " + (end - begin) + " 毫秒");
System.out.println(destBean.getPid());
System.out.println(destBean.getUserId());
System.out.println(destBean.getSubTitle());
System.out.println(destBean.getAlias());
System.out.println(destBean.getActor());
System.out.println(destBean.getShortDesc());
System.out.println("----------------------------------------");
}
}
View Code
package test;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.beanutils.PropertyUtils;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.BeanUtils;
import net.sf.cglib.beans.BeanCopier;
/**
*
* @author wy
*
*/
public class PerformanceTest {
public SourceBean sourceBean = null;
public IMethodCallBack manualCopy = null;
public IMethodCallBack cglib = null;
public IMethodCallBack springBeanUtils = null;
public IMethodCallBack apachePropertyUtils = null;
public IMethodCallBack apacheBeanUtils = null;
@Before
public void init() {
// 初始化数据
sourceBean = new SourceBean();
sourceBean.setPid(Long.valueOf(1001));
sourceBean.setUserId(Long.valueOf(123));
sourceBean.setSubTitle("人再囧途之港囧");
sourceBean.setAlias("港囧");
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("主演1", "徐峥");
map.put("主演2", "包贝尔");
map.put("主演3", "赵薇");
sourceBean.setActor(map);
sourceBean.setShortDesc("徐来和小舅子抱着各自不同目的来到香港,展开了一段阴差阳错、啼笑皆非的旅程,最终两人获得友谊并懂得了人生真谛。");
// 手动设置属性
manualCopy = new IMethodCallBack() {
@Override
public String getMethodName() {
return "manual copy";
}
@Override
public DestBean callBack(SourceBean sourceBean) throws Exception {
DestBean destBean = new DestBean();
destBean.setActor(sourceBean.getActor());
destBean.setPid(sourceBean.getPid());
destBean.setUserId(sourceBean.getUserId().intValue());
destBean.setShortDesc(sourceBean.getShortDesc());
destBean.setSubTitle(sourceBean.getSubTitle());
destBean.setAlias(sourceBean.getAlias());
return destBean;
}
};
// Cglib
cglib = new IMethodCallBack() {
BeanCopier beanCopier = BeanCopier.create(SourceBean.class, DestBean.class, false);
@Override
public String getMethodName() {
return "net.sf.cglib.beans.BeanCopier.create";
}
@Override
public DestBean callBack(SourceBean sourceBean) throws Exception {
DestBean destBean = new DestBean();
beanCopier.copy(sourceBean, destBean, null);
return destBean;
}
};
// Spring BeanUtils
springBeanUtils = new IMethodCallBack() {
@Override
public String getMethodName() {
return "org.springframework.beans.BeanUtils.copyProperties";
}
@Override
public DestBean callBack(SourceBean sourceBean) throws Exception {
DestBean destBean = new DestBean();
BeanUtils.copyProperties(sourceBean, destBean);
return destBean;
}
};
// Apache PropertyUtils
apachePropertyUtils = new IMethodCallBack() {
@Override
public String getMethodName() {
return "org.apache.commons.beanutils.PropertyUtils.copyProperties";
}
@Override
public DestBean callBack(SourceBean sourceBean) throws Exception {
DestBean destBean = new DestBean();
PropertyUtils.copyProperties(destBean, sourceBean);
return destBean;
}
};
// Apache BeanUtils
apacheBeanUtils = new IMethodCallBack() {
@Override
public String getMethodName() {
return "org.apache.commons.beanutils.BeanUtils.copyProperties";
}
@Override
public DestBean callBack(SourceBean sourceBean) throws Exception {
DestBean destBean = new DestBean();
org.apache.commons.beanutils.BeanUtils.copyProperties(destBean, sourceBean);
return destBean;
}
};
}
// 测试一百次性能测试
@Test
public void perform100() throws Exception {
CopyProcessor processor100 = new CopyProcessor(100);
processor100.processor(manualCopy, sourceBean);
processor100.processor(cglib, sourceBean);
processor100.processor(springBeanUtils, sourceBean);
processor100.processor(apachePropertyUtils, sourceBean);
processor100.processor(apacheBeanUtils, sourceBean);
CopyProcessor processor100R = new CopyProcessor(100);
processor100R.processor(apacheBeanUtils, sourceBean);
processor100R.processor(apachePropertyUtils, sourceBean);
processor100R.processor(springBeanUtils, sourceBean);
processor100R.processor(cglib, sourceBean);
processor100R.processor(manualCopy, sourceBean);
}
// 测试一千性能测试
@Test
public void perform1000() throws Exception {
CopyProcessor processor1000 = new CopyProcessor(1000);
processor1000.processor(manualCopy, sourceBean);
processor1000.processor(cglib, sourceBean);
processor1000.processor(springBeanUtils, sourceBean);
processor1000.processor(apachePropertyUtils, sourceBean);
processor1000.processor(apacheBeanUtils, sourceBean);
CopyProcessor processor1000R = new CopyProcessor(1000);
processor1000R.processor(apacheBeanUtils, sourceBean);
processor1000R.processor(apachePropertyUtils, sourceBean);
processor1000R.processor(springBeanUtils, sourceBean);
processor1000R.processor(cglib, sourceBean);
processor1000R.processor(manualCopy, sourceBean);
}
// 测试一万次性能测试
@Test
public void perform10000() throws Exception {
CopyProcessor processor10000 = new CopyProcessor(10000);
processor10000.processor(manualCopy, sourceBean);
processor10000.processor(cglib, sourceBean);
processor10000.processor(springBeanUtils, sourceBean);
processor10000.processor(apachePropertyUtils, sourceBean);
processor10000.processor(apacheBeanUtils, sourceBean);
CopyProcessor processor10000R = new CopyProcessor(10000);
processor10000R.processor(apacheBeanUtils, sourceBean);
processor10000R.processor(apachePropertyUtils, sourceBean);
processor10000R.processor(springBeanUtils, sourceBean);
processor10000R.processor(cglib, sourceBean);
processor10000R.processor(manualCopy, sourceBean);
}
// 测试十万次性能测试
@Test
public void perform100000() throws Exception {
CopyProcessor processor100000 = new CopyProcessor(100000);
processor100000.processor(manualCopy, sourceBean);
processor100000.processor(cglib, sourceBean);
processor100000.processor(springBeanUtils, sourceBean);
processor100000.processor(apachePropertyUtils, sourceBean);
processor100000.processor(apacheBeanUtils, sourceBean);
processor100000.processor(apacheBeanUtils, sourceBean);
processor100000.processor(apachePropertyUtils, sourceBean);
processor100000.processor(springBeanUtils, sourceBean);
processor100000.processor(cglib, sourceBean);
processor100000.processor(manualCopy, sourceBean);
}
// 测试一百万次性能测试
@Test
public void perform1000000() throws Exception {
CopyProcessor processor1000000 = new CopyProcessor(1000000);
processor1000000.processor(manualCopy, sourceBean);
processor1000000.processor(cglib, sourceBean);
processor1000000.processor(springBeanUtils, sourceBean);
processor1000000.processor(apachePropertyUtils, sourceBean);
processor1000000.processor(apacheBeanUtils, sourceBean);
CopyProcessor processor1000000R = new CopyProcessor(1000000);
processor1000000R.processor(apacheBeanUtils, sourceBean);
processor1000000R.processor(apachePropertyUtils, sourceBean);
processor1000000R.processor(springBeanUtils, sourceBean);
processor1000000R.processor(cglib, sourceBean);
processor1000000R.processor(manualCopy, sourceBean);
}
}
View Code
3、结果比较
一百次性能测试 | 第一次(毫秒) | 第二次(毫秒) | 第三次(毫秒) | 每次平均值(毫秒) |
manualCopy | 0 | 1 | 1 | 0.0066666666666667 |
cglib | 1 | 2 | 2 | 0.0166666666666667 |
springBeanUtils | 177 | 181 | 192 | 1.833333333333333 |
apachePropertyUtils | 179 | 207 | 192 | 1.926666666666667 |
apacheBeanUtils | 96 | 94 | 89 | 0.93 |
一千次性能测试 | 第一次(毫秒) | 第二次(毫秒) | 第三次(毫秒) | 每次平均值(毫秒) |
manualCopy | 1 | 1 | 2 | 0.0013333333333333 |
cglib | 13 | 11 | 12 | 0.012 |
springBeanUtils | 272 | 261 | 286 | 0.273 |
apachePropertyUtils | 450 | 431 | 444 | 0.4416666666666667 |
apacheBeanUtils | 349 | 353 | 360 | 0.354 |
一万次性能测试 | 第一次(毫秒) | 第二次(毫秒) | 第三次(毫秒) | 每次平均值(毫秒) |
manualCopy | 2 | 3 | 4 | 0.0003 |
cglib | 16 | 18 | 17 | 0.0016 |
springBeanUtils | 526 | 554 | 532 | 0.0537333333333333 |
apachePropertyUtils | 1888 | 1848 | 1832 | 0.1856 |
apacheBeanUtils | 2210 | 2150 | 2162 | 0.2174 |
十万次性能测试 | 第一次(毫秒) | 第二次(毫秒) | 第三次(毫秒) | 每次平均值(毫秒) |
manualCopy | 26 | 24 | 26 | 0.00025333 |
cglib | 48 | 51 | 48 | 0.00049 |
springBeanUtils | 1949 | 1956 | 1881 | 0.0192866666666667 |
apachePropertyUtils | 14741 | 15478 | 15065 | 0.1509466666666667 |
apacheBeanUtils | 19506 | 19800 | 19753 | 0.1968633333333333 |
输出结果: manualCopy > cglibCopy > springBeanUtils > apachePropertyUtils > apacheBeanUtils 可以理解为: 手工复制 > cglib > 反射。
对于最求速度的属性拷贝,建议使用手动设置拷贝,虽然代码会变得臃肿不堪。
4、原理说明
反射类型:
都使用静态类调用,最终转化虚拟机中两个单例的工具对象。
public BeanUtilsBean()
{
this(new ConvertUtilsBean(), new PropertyUtilsBean());
}
ConvertUtilsBean可以通过ConvertUtils全局自定义注册。
ConvertUtils.register(new DateConvert(), java.util.Date.class);
PropertyUtilsBean的copyProperties方法实现了拷贝的算法。
1、 动态bean:orig instanceof DynaBean:Object value = ((DynaBean)orig).get(name);然后把value复制到动态bean类
2、 Map类型:orig instanceof Map:key值逐个拷贝
3、 其他普通类::从beanInfo【每一个对象都有一个缓存的bean信息,包含属性字段等】取出name,然后把sourceClass和targetClass逐个拷贝
Cglib类型:BeanCopier
copier = BeanCopier.create(source.getClass(), target.getClass(), false);
copier.copy(source, target, null);
Create对象过程:产生sourceClass-》TargetClass的拷贝代理类,放入jvm中,所以创建的代理类的时候比较耗时。最好保证这个对象的单例模式,可以参照最后一部分的优化方案。
创建过程:源代码见jdk:net.sf.cglib.beans.BeanCopier.Generator.generateClass(ClassVisitor)
1、 获取sourceClass的所有public get 方法-》PropertyDescriptor[] getters
2、 获取TargetClass 的所有 public set 方法-》PropertyDescriptor[] setters
3、 遍历setters的每一个属性,执行4和5
4、 按setters的name生成sourceClass的所有setter方法-》PropertyDescriptor getter【不符合javabean规范的类将会可能出现空指针异常】
5、 PropertyDescriptor[] setters-》PropertyDescriptor setter
6、 将setter和getter名字和类型 配对,生成代理类的拷贝方法。
Copy属性过程:调用生成的代理类,代理类的代码和手工操作的代码很类似,效率非常高。
Apache BeanUtils.copyProperties会进行类型转换,而Apache PropertyUtils.copyProperties不会。 既然进行了类型转换,那BeanUtils.copyProperties的速度比不上PropertyUtils.copyProperties。我们从上面的实践中得到了验证。
三、注意事项
注意事项 | 是否支持扩展useConvete功能 | 相同属性名,且类型不匹配时候的处理 | Set和Get方法不匹配的处理 | 对于空字段的处理 | |
manualCopy | ---- | ---- | ---- | ---- | |
cglib | 支持 | 只拷贝名称和类型都相同的属性, 名称相同而类型不同的属性不会被拷贝 | OK | 包装类型未设置值字段,默认设置null | |
springBeanUtils | 支持 | 能正常拷贝并进行初级转换,Long和Integer互转 | OK | | |
apachePropertyUtils | 不支持 | 异常 java.lang.IllegalArgumentException: argument type mismatch | OK | | |
apacheBeanUtils | 支持 | 能正常拷贝并进行初级转换,Long和Integer互转 | OK | | |
对于未设置值的field字段,无论是基本数据类型还是包装类型、集合的值都是各自的默认值。
四、总结
1、追求高效率的属性拷贝请使用手工设置属性(set)
2、在使用工具进行属性拷贝时,要注意程序的健壮性即日期Date、各种类型的变量的初始值。