标签:
一、对象属性拷贝工具类
”天下武功,唯快不破“。在互联网行业中体现的更加淋淋尽致。我们在业务系统会经常遇到业务对象间属性的拷贝,对如外接口一般都使用特定的DTO对象,而不会使用领域模型,以避免两者的变动互相影响。我们不仅要关注“快”,还要注重CPU的稳定即避免CPU使用的大起大落现象。如何高效完成属性的拷贝并降低对CPU的使用率或避免CPU的抖动。
相关博文已经有很多,为什么还要自己在一篇类似的哪?原因有二:一是加深理解二是比较各自优劣。目前对象间属性的拷贝常用的方法大致如下:
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; }
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("----------------------------------------"); } }
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); } }
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、各种类型的变量的初始值。
参考:http://grefr.iteye.com/blog/1880008
由于本人经验有限,文章中难免会有错误,请浏览文章的您指正或有不同的观点共同探讨!
标签:
原文地址:http://www.cnblogs.com/exceptioneye/p/4852962.html