一、事出有因
前段時(shí)間阿里發(fā)布了阿里巴巴代碼規(guī)約插件,果斷將它集成起來。右鍵->阿里編碼規(guī)約掃描,立即將不符合阿里編程規(guī)范的代碼現(xiàn)了原形,不得不服阿里想統(tǒng)一整個(gè)java市場的決心啊。怎么?竟然看到我最喜歡使用的Apache BeanUtils.copyProperties()方法后面打了個(gè)大大的紅叉,提示"避免使用Apache的BeanUtils進(jìn)行屬性的copy"。心里確實(shí)不是滋味,從小老師就教導(dǎo)我們,"凡是Apache寫的框架都是好框架",怎么可能會(huì)存在"性能問題"--還是這種猿們所不能容忍的問題。心存著對(duì)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執(zhí)行" + 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 耗時(shí): " + 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執(zhí)行" + len + "次================");
DestinationObject destination2 = new DestinationObject();
for (int i = 0; i < len; i++) {
BeanUtils.copyProperties(destination2, origin);
}
stopwatch.stop();
System.out.println("testApacheBeanUtils 耗時(shí): " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
private static void testSpringFramework(OriginObject origin, int len) {
Stopwatch stopwatch = Stopwatch.createStarted();
System.out.println("================springframework執(zhí)行" + 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 耗時(shí): " + 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執(zhí)行" + len + "次================");
DestinationObject destination2 = new DestinationObject();
for (int i = 0; i < len; i++) {
PropertyUtils.copyProperties(destination2, origin);
}
stopwatch.stop();
System.out.println("testApacheBeanUtilsPropertyUtils 耗時(shí): " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
分別執(zhí)行1000、10000、100000、1000000次耗時(shí)數(shù)(毫秒):
| 工具名稱 | 執(zhí)行1000次耗時(shí) | 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 |
結(jié)論:
- Apache BeanUtils的性能最差,不建議使用。
- Apache PropertyUtils100000次以內(nèi)性能還能接受,到百萬級(jí)別性能就比較差了,可酌情考慮。
- spring BeanUtils和BeanCopier性能較好,如果對(duì)性能有特別要求,可使用BeanCopier,不然spring BeanUtils也是可取的。
四、Apache BeanUtils.copyProperties()性能分析
執(zhí)行1000000次copy屬性,然后通過jvisualvm查看方法耗時(shí),如圖:

發(fā)現(xiàn)最耗時(shí)的方法就是method.invoke(),但是spring的BeanUtils、PropertyUtils里也是采用反射來實(shí)現(xiàn)的,為什么效率相差這么大呢?

看來Abstract.convert()和getIntrospectionData()占用了很大一部分時(shí)間.而且Apache BeanUtils中的日志輸出也比較耗時(shí)。
五、看看Spring BeanUtils和PropertyUtils

PropertyUtils和Apache BeanUtils核心代碼區(qū)別在圖中標(biāo)注的地方。Apache BeanUtils主要集中了各種豐富的功能(日志、轉(zhuǎn)換、解析等等),導(dǎo)致性能變差。
而Spring BeanUtils則是直接通過反射來讀取和寫入,直抒胸臆,省去了其他繁雜的步驟,性能自然不差。

六、Cglib BeanCopier
cglib BeanCopier的主要耗時(shí)方法就在BeanCopier.create(),如果將該方法做成靜態(tài)成員變量,則還可以大大縮小執(zhí)行時(shí)間。BeanCopier是一種基于字節(jié)碼的方式,其實(shí)就是通過字節(jié)碼方式轉(zhuǎn)換成性能最好的get、set方式,只需考慮創(chuàng)建BeanCopier的開銷,如果我們將BeanCopier做成靜態(tài)的,基本只需考慮get、set的開銷,所以性能接近于get、set。BeanCopier源碼分析可見:http://www.itdecent.cn/p/f8b892e08d26,我這里就不展開了。