一、事出有因
前段时间阿里发布了阿里巴巴代码规约插件,果断将它集成起来。右键->阿里编码规约扫描,立即将不符合阿里编程规范的代码现了原形,不得不服阿里想统一整个java市场的决心啊。怎么?竟然看到我最喜欢使用的Apache BeanUtils.copyProperties()方法后面打了个大大的红叉,提示"避免使用Apache的BeanUtils进行属性的copy"。心里确实不是滋味,从小老师就教导我们,"凡是Apache写的框架都是好框架",怎么可能会存在"性能问题"--还是这种猿们所不能容忍的问题。心存着对BeanUtils的怀疑开始了今天的研究之路。
二、市面上的其他几种属性copy工具
- springframework的BeanUtils
- cglib的BeanCopier
- Apache BeanUtils包的PropertyUtils类
三、下面来测试一下性能。
private static void testCglibBeanCopier(OriginObject origin, int len) {
Stopwatch stopwatch = Stopwatch.createStarted();
System.out.println();
System.out.println("================cglib BeanCopier执行" + len + "次================");
DestinationObject destination3 = new DestinationObject();
for (int i = 0; i < len; i++) {
BeanCopier copier = BeanCopier.create(OriginObject.class, DestinationObject.class, false);
copier.copy(origin, destination3, null);
}
stopwatch.stop();
System.out.println("testCglibBeanCopier 耗时: " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
private static void testApacheBeanUtils(OriginObject origin, int len)
throws IllegalAccessException, InvocationTargetException {
Stopwatch stopwatch = Stopwatch.createStarted();
System.out.println();
System.out.println("================apache BeanUtils执行" + len + "次================");
DestinationObject destination2 = new DestinationObject();
for (int i = 0; i < len; i++) {
BeanUtils.copyProperties(destination2, origin);
}
stopwatch.stop();
System.out.println("testApacheBeanUtils 耗时: " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
private static void testSpringFramework(OriginObject origin, int len) {
Stopwatch stopwatch = Stopwatch.createStarted();
System.out.println("================springframework执行" + len + "次================");
DestinationObject destination = new DestinationObject();
for (int i = 0; i < len; i++) {
org.springframework.beans.BeanUtils.copyProperties(origin, destination);
}
stopwatch.stop();
System.out.println("testSpringFramework 耗时: " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
private static void testApacheBeanUtilsPropertyUtils(OriginObject origin, int len)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Stopwatch stopwatch = Stopwatch.createStarted();
System.out.println();
System.out.println("================apache BeanUtils PropertyUtils执行" + len + "次================");
DestinationObject destination2 = new DestinationObject();
for (int i = 0; i < len; i++) {
PropertyUtils.copyProperties(destination2, origin);
}
stopwatch.stop();
System.out.println("testApacheBeanUtilsPropertyUtils 耗时: " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
分别执行1000、10000、100000、1000000次耗时数(毫秒):
工具名称 | 执行1000次耗时 | 10000次 | 100000次 | 1000000次 |
---|---|---|---|---|
Apache BeanUtils | 390ms | 854ms | 1763ms | 8408ms |
Apache PropertyUtils | 26ms | 221ms | 352ms | 2663ms |
spring BeanUtils | 39ms | 315ms | 373ms | 949ms |
Cglib BeanCopier | 64ms | 144ms | 171ms | 309ms |
结论:
- Apache BeanUtils的性能最差,不建议使用。
- Apache PropertyUtils100000次以内性能还能接受,到百万级别性能就比较差了,可酌情考虑。
- spring BeanUtils和BeanCopier性能较好,如果对性能有特别要求,可使用BeanCopier,不然spring BeanUtils也是可取的。
四、Apache BeanUtils.copyProperties()性能分析
执行1000000次copy属性,然后通过jvisualvm查看方法耗时,如图:
发现最耗时的方法就是method.invoke(),但是spring的BeanUtils、PropertyUtils里也是采用反射来实现的,为什么效率相差这么大呢?
看来Abstract.convert()和getIntrospectionData()占用了很大一部分时间.而且Apache BeanUtils中的日志输出也比较耗时。
五、看看Spring BeanUtils和PropertyUtils
PropertyUtils和Apache BeanUtils核心代码区别在图中标注的地方。Apache BeanUtils主要集中了各种丰富的功能(日志、转换、解析等等),导致性能变差。
而Spring BeanUtils则是直接通过反射来读取和写入,直抒胸臆,省去了其他繁杂的步骤,性能自然不差。
六、Cglib BeanCopier
cglib BeanCopier的主要耗时方法就在BeanCopier.create(),如果将该方法做成静态成员变量,则还可以大大缩小执行时间。BeanCopier是一种基于字节码的方式,其实就是通过字节码方式转换成性能最好的get、set方式,只需考虑创建BeanCopier的开销,如果我们将BeanCopier做成静态的,基本只需考虑get、set的开销,所以性能接近于get、set。BeanCopier源码分析可见://www.greatytc.com/p/f8b892e08d26,我这里就不展开了。