摘要
本文介绍四种主流的属性拷贝工具:
- PropertyUtils (commons beanutils)
- BeanUtils (commons beanutils)
- BeanUtils (Spring beans)
- BeanCopier (cglib)
它们的属性拷贝都基于java内省,拷贝的对象类型必须提供setter/getter;
经过拷贝速度测试:
- getter/setter 速度最快,但是不适用于数量多的属性拷贝;
- BeanCopier 速度位居第二,在使用缓存的情况下,甚至与getter/setter相差无几;
- Spring BeanUtils 在次数增加的情况下,速度较好,在次数少时,比commons beanutils的两种工具都要慢;
- PropertyUtils 比BeanUtils 速度快,更稳定。
一、属性拷贝
属性拷贝
日常开发中,经常会有将一个java bean的属性拷贝到另一个java bean的需求。这两个bean可能是相同类型;也可能是不同类型,但是有部分相同字段。
最直观的做法是使用setter/getter:
objA.setName(objB.getName());
这种方法效率是最高的;但是当要拷贝的属性较多时,setter/getter就不方便,不优雅了。
有需求就会有相应的解决方案,以下四种主流工具提供了属性拷贝功能:
- 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
外,其他三个类都提供了静态方法直接调用。
cglib的net.sf.cglib.beans.BeanCopier
稍微麻烦一点,需要先创建copier:
BeanCopier beanCopier = net.sf.cglib.beans.BeanCopier.create(SourceBean.class, TargetBean.class, false);
beanCopier.copy(from, to, null);
四种工具的属性拷贝原理,都是利用了java内省机制;所以,需要拷贝的java bean类型都必须提供getter以及setter方法,否者拷贝失败。
同名不同类型
拷贝时,遇到同名属性,但是类型不同,几种工具表现也不同:
org.apache.commons.beanutils.PropertyUtils#copyProperties()
(同名setter,类型不同,异常)
org.apache.commons.beanutils.BeanUtils#copyProperties()
(同名setter,即使类型不同也能转换,但是值可能不正确)
org.springframework.beans.BeanUtils#copyProperties()
(同名setter,即使类型不同也能转换,但是值可能不正确)
net.sf.cglib.beans.BeanCopier
(同名setter,即使类型不同也能转换,但是值可能不正确)
二、速度比较
经过代码测试, 10次,1000次,1000000次各种手段的速度表现如下:
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 |
可以得出结论:
- getter/setter 速度最快,但是不适用于数量多的属性拷贝;
- BeanCopier 速度位居第二,在使用缓存的情况下,甚至与getter/setter相差无几;即使不使用缓存,速度也很快;
- Spring BeanUtils 在次数增加的情况下,速度较好,在次数少时,比commons beanutils的两种工具都要慢;
- PropertyUtils 比BeanUtils 速度快,更稳定。
需要注意一点,BeanCopier#create开销比较大,所以不要频繁创建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("执行%d次用时%dms---------%s%n", time, System.currentTimeMillis() - startTime, type);
}
interface BeanCopy {
void copy(SourceBean from, TargetBean to) throws Exception;
}
}