# 為什么禁止使用 Apache Beanutils 進行屬性的 copy ?
在日常開發中,我們經常需要給對象進行賦值,通常會調用其 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);?
}
```
獲得結果以下:
| 工具類 | 執行1000次耗時 | 執行10000次耗時 | 執行100000次耗時 | 執行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>
```
- java開發手冊
- 嵩山版手冊
- 為什么禁止使用 Apache Beanutils 進行屬性的 copy ?
- 為什么阿里巴巴要求日期格式化時必須有使用y表示年,而不能用Y?
- 《Java 開發手冊 - 泰山版》提到的三目運算符 的空指針問題到底是個怎么回事?
- 為什么建議初始化 HashMap 的容量大小?
- Java 開發手冊建議創建 HashMap 時設置初始 化容量,但是多少合適呢?
- 為什么禁止使用 Executors 創建線程池?
- 為什么要求謹慎使用 ArrayList 中的 subList 方法?
- 為什么不建議在 for 循環中使用“+”進行字符 串拼接?
- 為什么禁止在 foreach 循環里進行元素的 remove/add 操作?
- 為什么禁止工程師直接使用日志系統 (Log4j、 Logback) 中的 API ?
- 為什么禁止把 SimpleDateFormat 定義成 static 變量?
- 為什么禁止開發人員使用 isSuccess 作為變量名?
- 為什么禁止開發人員修改 serialVersionUID 字段的值?
- 為什么建議開發者謹慎使用繼承?
- 為什么禁止使用 count( 列名 ) 或 count( 常量 ) 來替代 count(*) ?