摘要
本文介紹四種主流的屬性拷貝工具:
- PropertyUtils (commons beanutils)
- BeanUtils (commons beanutils)
- BeanUtils (Spring beans)
- BeanCopier (cglib)
它們的屬性拷貝都基于java內(nèi)省,拷貝的對象類型必須提供setter/getter;
經(jīng)過拷貝速度測試:
- getter/setter 速度最快,但是不適用于數(shù)量多的屬性拷貝;
- BeanCopier 速度位居第二,在使用緩存的情況下,甚至與getter/setter相差無幾;
- Spring BeanUtils 在次數(shù)增加的情況下,速度較好,在次數(shù)少時,比commons beanutils的兩種工具都要慢;
- PropertyUtils 比BeanUtils 速度快,更穩(wěn)定。
一、屬性拷貝
屬性拷貝
日常開發(fā)中,經(jīng)常會有將一個java bean的屬性拷貝到另一個java bean的需求。這兩個bean可能是相同類型;也可能是不同類型,但是有部分相同字段。
最直觀的做法是使用setter/getter:
objA.setName(objB.getName());
這種方法效率是最高的;但是當(dāng)要拷貝的屬性較多時,setter/getter就不方便,不優(yōu)雅了。
有需求就會有相應(yīng)的解決方案,以下四種主流工具提供了屬性拷貝功能:
- org.apache.commons.beanutils.PropertyUtils#copyProperties()
- org.apache.commons.beanutils.BeanUtils#copyProperties()
- org.springframework.beans.BeanUtils#copyProperties()
- net.sf.cglib.beans.BeanCopier
依賴
org.apache.commons.beanutils.PropertyUtils.copyProperties以及org.apache.commons.beanutils.BeanUtils.copyProperties都由commons-beanutils提供:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
org.springframework.beans.BeanUtils.copyProperties由spring-beans提供:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
net.sf.cglib.beans.BeanCopier由cglib提供:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.7</version>
</dependency>
使用方法
屬性拷貝工具的使用方法都比較簡單,除了BeanCopier外,其他三個類都提供了靜態(tài)方法直接調(diào)用。
cglib的net.sf.cglib.beans.BeanCopier稍微麻煩一點,需要先創(chuàng)建copier:
BeanCopier beanCopier = net.sf.cglib.beans.BeanCopier.create(SourceBean.class, TargetBean.class, false);
beanCopier.copy(from, to, null);
四種工具的屬性拷貝原理,都是利用了java內(nèi)省機制;所以,需要拷貝的java bean類型都必須提供getter以及setter方法,否者拷貝失敗。
同名不同類型
拷貝時,遇到同名屬性,但是類型不同,幾種工具表現(xiàn)也不同:
org.apache.commons.beanutils.PropertyUtils#copyProperties() (同名setter,類型不同,異常)
org.apache.commons.beanutils.BeanUtils#copyProperties() (同名setter,即使類型不同也能轉(zhuǎn)換,但是值可能不正確)
org.springframework.beans.BeanUtils#copyProperties() (同名setter,即使類型不同也能轉(zhuǎn)換,但是值可能不正確)
net.sf.cglib.beans.BeanCopier(同名setter,即使類型不同也能轉(zhuǎn)換,但是值可能不正確)
二、速度比較
經(jīng)過代碼測試, 10次,1000次,1000000次各種手段的速度表現(xiàn)如下:
| 10次測試 | 第一次 | 第二次 | 第三次 | 平均值 | 每次平均值 |
|---|---|---|---|---|---|
| PropertyUtils.copyProperties | 0 | 1 | 0 | 1/3 | 0.033 |
| BeanUtils.copyProperties | 2 | 3 | 2 | 2.33 | 0.233 |
| SPRING copyProperties | 0 | 0 | 0 | 0 | 0 |
| BeanCopier(without cache) | 0 | 0 | 0 | 0 | 0 |
| BeanCopier | 0 | 0 | 0 | 0 | 0 |
| getter/setter | 0 | 0 | 0 | 0 | 0 |
| 1000次測試 | 第一次 | 第二次 | 第三次 | 平均值 | 每次平均值 |
|---|---|---|---|---|---|
| PropertyUtils.copyProperties | 22 | 21 | 16 | 19.66 | 0.01966 |
| BeanUtils.copyProperties | 19 | 29 | 20 | 22.66 | 0.02266 |
| SPRING copyProperties | 7 | 5 | 5 | 5.66 | 0.00566 |
| BeanCopier(without cache) | 4 | 4 | 3 | 3.66 | 0.00366 |
| BeanCopier | 0 | 0 | 1 | 0.33 | 0.00033 |
| getter/setter | 0 | 0 | 0 | 0 | 0 |
| 1000000次測試 | 第一次 | 第二次 | 第三次 | 平均值 | 每次平均值 |
|---|---|---|---|---|---|
| PropertyUtils.copyProperties | 1798 | 1770 | 1804 | 1767.33 | 0.00176733 |
| BeanUtils.copyProperties | 2904 | 2937 | 2860 | 2900.33 | 0.00290033 |
| SPRING copyProperties | 214 | 206 | 199 | 206.33 | 0.00020633 |
| BeanCopier(without cache) | 92 | 82 | 89 | 87.66 | 0.00008766 |
| BeanCopier | 8 | 7 | 8 | 7.66 | 0.00000766 |
| getter/setter | 8 | 7 | 7 | 7.33 | 0.00000733 |
可以得出結(jié)論:
- getter/setter 速度最快,但是不適用于數(shù)量多的屬性拷貝;
- BeanCopier 速度位居第二,在使用緩存的情況下,甚至與getter/setter相差無幾;即使不使用緩存,速度也很快;
- Spring BeanUtils 在次數(shù)增加的情況下,速度較好,在次數(shù)少時,比commons beanutils的兩種工具都要慢;
- PropertyUtils 比BeanUtils 速度快,更穩(wěn)定。
需要注意一點,BeanCopier#create開銷比較大,所以不要頻繁創(chuàng)建beanCopier實體,最使用緩存。
附
測試速度的代碼:
public class BeanCopyCompareTest {
private static BeanCopier beanCopier = net.sf.cglib.beans.BeanCopier.create(SourceBean.class, TargetBean.class, false);
public static void main(String[] args) throws Exception {
int[] times = {1, 10, 1000, 1000000};
for (int time : times) {
doCopy(time, (from, to) -> org.apache.commons.beanutils.PropertyUtils.copyProperties(to, from),
"org.apache.commons.beanutils.PropertyUtils.copyProperties");
doCopy(time, (from, to) -> org.apache.commons.beanutils.BeanUtils.copyProperties(to, from),
"org.apache.commons.beanutils.BeanUtils.copyProperties");
doCopy(time, (BeanUtils::copyProperties),
"org.springframework.beans.BeanUtils.copyProperties");
doCopy(time, ((from, to) -> beanCopier.copy(from, to, null)), "net.sf.cglib.beans.BeanCopier");
doCopy(time, ((from, to) -> {
BeanCopier beanCopier = net.sf.cglib.beans.BeanCopier.create(SourceBean.class, TargetBean.class, false);
beanCopier.copy(from, to, null);
}), "net.sf.cglib.beans.BeanCopier(without cache)");
doCopy(time, ((from, to) -> {
to.setAge(from.getAge());
to.setGender(from.getGender());
to.setName(from.getName());
to.setRight(from.getRight());
}), "getter/setter");
System.out.println("-----------------------------------------------------");
}
}
static void doCopy(Integer time, BeanCopy beanCopy, String type) throws Exception {
long startTime = System.currentTimeMillis();
for (int j = 0; j < time; j++) {
SourceBean sourceBean = new SourceBean();
sourceBean.setName("Richard");
sourceBean.setGender('F');
sourceBean.setAge(20);
sourceBean.setRight(true);
TargetBean targetBean = new TargetBean();
beanCopy.copy(sourceBean, targetBean);
}
System.out.printf("執(zhí)行%d次用時%dms---------%s%n", time, System.currentTimeMillis() - startTime, type);
}
interface BeanCopy {
void copy(SourceBean from, TargetBean to) throws Exception;
}
}