阿里巴巴为什么禁止使用Apache Beanutils进行属性复制?
作者 l Hollis
來源 l Hollis(ID:hollischuang)
在日常開發中,我們經常需要給對象進行賦值,通常會調用其set/get方法,有些時候,如果我們要轉換的兩個對象之間屬性大致相同,會考慮使用屬性拷貝工具進行。
如我們經常在代碼中會對一個數據結構封裝成DO、SDO、DTO、VO等,而這些Bean中的大部分屬性都是一樣的,所以使用屬性拷貝類工具可以幫助我們節省大量的set和get操作。
市面上有很多類似的工具類,比較常用的有
1、Spring BeanUtils?
2、Cglib BeanCopier?
3、Apache BeanUtils?
4、Apache PropertyUtils?
5、Dozer
那么,我們到底應該選擇哪種工具類更加合適呢?為什么阿里巴巴Java開發手冊中提到禁止使用Apache BeanUtils呢?
由于篇幅優先,關于這幾種工具類的用法及區別,還有到底是什么是淺拷貝和深拷貝不在本文的討論范圍內。
本文主要聚焦于對比這幾個類庫的性能問題。
性能對比
No Data No BB,我們就來寫代碼來對比下這幾種框架的性能情況。
代碼示例如下:
首先定義一個PersonDO類:
public?class?PersonDO?{private?Integer?id;private?String?name;private?Integer?age;private?Date?birthday;//省略setter/getter}再定義一個PersonDTO類:
public?class?PersonDTO?{private?String?name;private?Integer?age;private?Date?birthday;}然后進行測試類的編寫:
使用Spring BeanUtils進行屬性拷貝:
private?void?mappingBySpringBeanUtils(PersonDO?personDO,?int?times)?{StopWatch?stopwatch?=?new?StopWatch();stopwatch.start();for?(int?i?=?0;?i?<?times;?i++)?{PersonDTO?personDTO?=?new?PersonDTO();org.springframework.beans.BeanUtils.copyProperties(personDO,?personDTO);}stopwatch.stop();System.out.println("mappingBySpringBeanUtils?cost?:"?+?stopwatch.getTotalTimeMillis());}其中的StopWatch用于記錄代碼執行時間,方便進行對比。
使用Cglib BeanCopier進行屬性拷貝:
private?void?mappingByCglibBeanCopier(PersonDO?personDO,?int?times)?{StopWatch?stopwatch?=?new?StopWatch();stopwatch.start();for?(int?i?=?0;?i?<?times;?i++)?{PersonDTO?personDTO?=?new?PersonDTO();BeanCopier?copier?=?BeanCopier.create(PersonDO.class,?PersonDTO.class,?false);copier.copy(personDO,?personDTO,?null);}stopwatch.stop();System.out.println("mappingByCglibBeanCopier?cost?:"?+?stopwatch.getTotalTimeMillis());}使用Apache BeanUtils進行屬性拷貝:
private?void?mappingByApacheBeanUtils(PersonDO?personDO,?int?times)throws?InvocationTargetException,?IllegalAccessException?{StopWatch?stopwatch?=?new?StopWatch();stopwatch.start();for?(int?i?=?0;?i?<?times;?i++)?{PersonDTO?personDTO?=?new?PersonDTO();BeanUtils.copyProperties(personDTO,?personDO);}stopwatch.stop();System.out.println("mappingByApacheBeanUtils?cost?:"?+?stopwatch.getTotalTimeMillis());}使用Apache PropertyUtils進行屬性拷貝:
private?void?mappingByApachePropertyUtils(PersonDO?personDO,?int?times)throws?InvocationTargetException,?IllegalAccessException,?NoSuchMethodException?{StopWatch?stopwatch?=?new?StopWatch();stopwatch.start();for?(int?i?=?0;?i?<?times;?i++)?{PersonDTO?personDTO?=?new?PersonDTO();PropertyUtils.copyProperties(personDTO,?personDO);}stopwatch.stop();System.out.println("mappingByApachePropertyUtils?cost?:"?+?stopwatch.getTotalTimeMillis());}然后執行以下代碼:
public?static?void?main(String[]?args)throws?InvocationTargetException,?IllegalAccessException,?NoSuchMethodException?{PersonDO?personDO?=?new?PersonDO();personDO.setName("Hollis");personDO.setAge(26);personDO.setBirthday(new?Date());personDO.setId(1);MapperTest?mapperTest?=?new?MapperTest();mapperTest.mappingBySpringBeanUtils(personDO,?100);mapperTest.mappingBySpringBeanUtils(personDO,?1000);mapperTest.mappingBySpringBeanUtils(personDO,?10000);mapperTest.mappingBySpringBeanUtils(personDO,?100000);mapperTest.mappingBySpringBeanUtils(personDO,?1000000);mapperTest.mappingByCglibBeanCopier(personDO,?100);mapperTest.mappingByCglibBeanCopier(personDO,?1000);mapperTest.mappingByCglibBeanCopier(personDO,?10000);mapperTest.mappingByCglibBeanCopier(personDO,?100000);mapperTest.mappingByCglibBeanCopier(personDO,?1000000);mapperTest.mappingByApachePropertyUtils(personDO,?100);mapperTest.mappingByApachePropertyUtils(personDO,?1000);mapperTest.mappingByApachePropertyUtils(personDO,?10000);mapperTest.mappingByApachePropertyUtils(personDO,?100000);mapperTest.mappingByApachePropertyUtils(personDO,?1000000);mapperTest.mappingByApacheBeanUtils(personDO,?100);mapperTest.mappingByApacheBeanUtils(personDO,?1000);mapperTest.mappingByApacheBeanUtils(personDO,?10000);mapperTest.mappingByApacheBeanUtils(personDO,?100000);mapperTest.mappingByApacheBeanUtils(personDO,?1000000);}得到結果如下:
| Spring BeanUtils | 5ms | 10ms | 45ms | 169ms |
| Cglib BeanCopier | 4ms | 18ms | 45ms | 91ms |
| Apache PropertyUtils | 60ms | 265ms | 1444ms | 11492ms |
| Apache BeanUtils | 138ms | 816ms | 4154ms | 36938ms |
| Dozer | 566ms | 2254ms | 11136ms | 102965ms |
畫了一張折線圖更方便大家進行對比
綜上,我們基本可以得出結論,在性能方面,Spring BeanUtils和Cglib BeanCopier表現比較不錯,而Apache PropertyUtils、Apache BeanUtils以及Dozer則表現的很不好。
所以,如果考慮性能情況的話,建議大家不要選擇Apache PropertyUtils、Apache BeanUtils以及Dozer等工具類。
很多人會不理解,為什么大名鼎鼎的Apache開源出來的的類庫性能確不高呢?這不像是Apache的風格呀,這背后導致性能低下的原因又是什么呢?
其實,是因為Apache BeanUtils力求做得完美, 在代碼中增加了非常多的校驗、兼容、日志打印等代碼,過度的包裝導致性能下降嚴重。
總結
本文通過對比幾種常見的屬性拷貝的類庫,分析得出了這些工具類的性能情況,最終也驗證了《阿里巴巴Java開發手冊》中提到的"Apache BeanUtils 效率低"的事實。
但是本文只是站在性能這一單一角度進行了對比,我們在選擇一個工具類的時候還會有其他方面的考慮,比如使用成本、理解難度、兼容性、可擴展性等,對于這種拷貝類工具類,我們還會考慮其功能是否完善等。
就像雖然Dozer性能比較差,但是他可以很好的和Spring結合,可以通過配置文件等進行屬性之間的映射等,也受到了很多開發者的喜愛。
本文用到的第三方類庫的maven依賴如下:
<!--Apache?PropertyUtils、Apache?BeanUtils--><dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils</artifactId><version>1.9.4</version></dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.2</version></dependency><!--Spring?PropertyUtils--><dependency><groupId>org.springframework</groupId><artifactId>org.springframework.beans</artifactId><version>3.1.1.RELEASE</version></dependency><!--cglib--><dependency><groupId>cglib</groupId><artifactId>cglib-nodep</artifactId><version>2.2.2</version></dependency><!--dozer--><dependency><groupId>net.sf.dozer</groupId><artifactId>dozer</artifactId><version>5.5.1</version></dependency><!--日志相關--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.7</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>jul-to-slf4j</artifactId><version>1.7.7</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>jcl-over-slf4j</artifactId><version>1.7.7</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>1.7.7</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-jdk14</artifactId><version>1.7.7</version></dependency> 往期推薦URL 去重的 6 種方案!(附詳細代碼)
多圖證明,Java到底是值傳遞還是引用傳遞?
阿里為什么推薦使用LongAdder,而不是volatile?
關注下方二維碼,收獲更多干貨!
總結
以上是生活随笔為你收集整理的阿里巴巴为什么禁止使用Apache Beanutils进行属性复制?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RabbitMQ事务和Confirm发送
- 下一篇: 调研了100+开源博客,发现这5个最好用