一汗唱、事出有因
前段時間阿里發(fā)布了阿里巴巴代碼規(guī)約插件,果斷將它集成起來。右鍵->阿里編碼規(guī)約掃描,立即將不符合阿里編程規(guī)范的代碼現(xiàn)了原形,不得不服阿里想統(tǒng)一整個java市場的決心啊逝慧。怎么熟掂?竟然看到我最喜歡使用的Apache BeanUtils.copyProperties()方法后面打了個大大的紅叉,提示"避免使用Apache的BeanUtils進(jìn)行屬性的copy"嗜侮。心里確實(shí)不是滋味,從小老師就教導(dǎo)我們,"凡是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執(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 耗時: " + 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 耗時: " + 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 耗時: " + 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 耗時: " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
分別執(zhí)行1000疲牵、10000承二、100000、1000000次耗時數(shù)(毫秒):
工具名稱 | 執(zhí)行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 |
結(jié)論:
- Apache BeanUtils的性能最差,不建議使用瑰步。
- Apache PropertyUtils100000次以內(nèi)性能還能接受,到百萬級別性能就比較差了,可酌情考慮矢洲。
- spring BeanUtils和BeanCopier性能較好,如果對性能有特別要求,可使用BeanCopier,不然spring BeanUtils也是可取的。
四缩焦、Apache BeanUtils.copyProperties()性能分析
執(zhí)行1000000次copy屬性,然后通過jvisualvm查看方法耗時,如圖:
發(fā)現(xiàn)最耗時的方法就是method.invoke(),但是spring的BeanUtils读虏、PropertyUtils里也是采用反射來實(shí)現(xiàn)的,為什么效率相差這么大呢?
看來Abstract.convert()和getIntrospectionData()占用了很大一部分時間.而且Apache BeanUtils中的日志輸出也比較耗時袁滥。
五盖桥、看看Spring BeanUtils和PropertyUtils
PropertyUtils和Apache BeanUtils核心代碼區(qū)別在圖中標(biāo)注的地方。Apache BeanUtils主要集中了各種豐富的功能(日志题翻、轉(zhuǎn)換揩徊、解析等等),導(dǎo)致性能變差。
而Spring BeanUtils則是直接通過反射來讀取和寫入,直抒胸臆,省去了其他繁雜的步驟,性能自然不差嵌赠。
六塑荒、Cglib BeanCopier
cglib BeanCopier的主要耗時方法就在BeanCopier.create(),如果將該方法做成靜態(tài)成員變量,則還可以大大縮小執(zhí)行時間。BeanCopier是一種基于字節(jié)碼的方式,其實(shí)就是通過字節(jié)碼方式轉(zhuǎn)換成性能最好的get姜挺、set方式,只需考慮創(chuàng)建BeanCopier的開銷,如果我們將BeanCopier做成靜態(tài)的,基本只需考慮get齿税、set的開銷,所以性能接近于get、set炊豪。BeanCopier源碼分析可見:http://www.reibang.com/p/f8b892e08d26,我這里就不展開了凌箕。