项目中的通常都会遇到bean copy的场景,spring,oracle,Apache都有自己的开源bean copy框架,大家常常会困扰到底哪个框架的性能最好,下面对各个框架的性能测试,帮助大家在开发中使用合适自己的bean copy框架:
目前项目中使用的的几种BeanCope的框架:
1.org.springframework.beans.BeanUtils,version 4.3.18-RELEASE
2.org.apache.commons.beanutils.PropertyUtils, version 1.9.3-RELEASE
3.org.apache.commons.beanutils.BeanUtil, version 1.9.3-RELEASE
4.cglib我用的是spring带的:org.springframework.cglib.beans.BeanCopier, version 4.3.18-RELEASE
5.ma.glasnost.orika.impl.DefaultMapperFactory, version 1.5.2-RELEASE
6.get/set
(直接想看结论的直接跳到最后)
内部实现原理:1,2,3前三个框架是都是通过反射原理实现,大致的方法就是通过反射获取字段名来匹配目标类的字段
4,5是通过字节码实现的,具体实现可以看相关源码
测试框架:JMH
# Warmup: 20 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 50 threads, will synchronize iterations
# Benchmark mode: Average time, time/op
预热20s,执行60s,线程50,模式为平均时间
测试类:
public class FromBean {
private String string1;
private String string2;
private Integer integer1;
private Long long1;
private Date date1;
}
public class ToBean {
private String string1;
private String string2;
private Integer integer1;
private Long long1;
private Date date1;
}
测试方法:
public void springbean(FromBean fromBean){
ToBean toBean = new ToBean();
try {
BeanUtils.copyProperties(fromBean, toBean);
} catch (Exception e){}
}
public void springbeanlom(FromBean fromBean){
ToBeanLombok toBean = new ToBeanLombok();
try {
BeanUtils.copyProperties(fromBean, toBean);
} catch (Exception e){}
}
public void apacheproperty(FromBean fromBean){
ToBean toBean = new ToBean();
try {
PropertyUtils.copyProperties(toBean, fromBean);
} catch (Exception e){}
}
public void cglib(FromBean fromBean){
ToBean toBean = new ToBean();
try {
BeanCopier beanCopier = BeanCopier.create(FromBean.class, ToBean.class, false);
beanCopier.copy(fromBean, toBean, null);
} catch (Exception e){}
}
public void orika(FromBean fromBean){
//MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); 每次创建消耗很大,先初始化该factory
try {
ToBean toBean = mapperFactory.getMapperFacade().map(fromBean, ToBean.class);
} catch (Exception e){}
}
public void getset(FromBean fromBean){
ToBean toBean = new ToBean();
try {
toBean.setString1(fromBean.getString1());
toBean.setString2(fromBean.getString2());
toBean.setInteger1(fromBean.getInteger1());
toBean.setLong1(fromBean.getLong1());
toBean.setDate1(fromBean.getDate1());
} catch (Exception e){}
}
测试1:单线程,5项属性:string,string,integer,long,date
org.springframework.beans.BeanUtils |
0.288 |
0.348 |
0.278 |
org.apache.commons.beanutils.PropertyUtils |
3.257 |
4.826 |
2.891 |
org.apache.commons.beanutils.BeanUtils |
5.467 |
6.121 |
5.366 |
org.springframework.cglib.beans.BeanCopier |
0.079 |
0.097 |
0.076 |
ma.glasnost.orika.impl.DefaultMapperFactory |
0.444 3819.167 |
0.453 7072.294 |
0.440 3208.739 |
get,set |
0.027 |
0.045 |
0.024 |
orika初始化mapperFactory 消耗的时间很长,下面面是每次都初始化的时间。
测试2:单线程,6项属性:string,string,integer,long,date + innerBean
在bean中新增了一个属性为innerBean:先看一下copy方法是不是深拷贝。
public class InnerBean {
private String stirng1;
private Integer integer1;
private Date localDate;
public String getStirng1() {
return stirng1;
}
public void setStirng1(String stirng1) {
this.stirng1 = stirng1;
}
public Integer getInteger1() {
return integer1;
}
public void setInteger1(Integer integer1) {
this.integer1 = integer1;
}
public Date getLocalDate() {
return localDate;
}
public void setLocalDate(Date localDate) {
this.localDate = localDate;
}
}
结果是只有orika的方法是深拷贝,其他都是直接引用的原来的innerBean。
org.springframework.beans.BeanUtils |
0.317 |
org.apache.commons.beanutils.PropertyUtils |
3.509 |
org.apache.commons.beanutils.BeanUtils | 5.996 |
org.springframework.cglib.beans.BeanCopier | 0.069 |
ma.glasnost.orika.impl.DefaultMapperFactory | 0.466 |
get,set | 0.001 |
测试3:20线程,6项属性:string,string,integer,long,date + innerBean
org.springframework.beans.BeanUtils |
2.909 |
org.apache.commons.beanutils.PropertyUtils |
38.671 |
org.apache.commons.beanutils.BeanUtils |
63.438 |
org.springframework.cglib.beans.BeanCopier | 0.638 |
ma.glasnost.orika.impl.DefaultMapperFactory | 10.472 |
get,set | 0.005 |
测试4:50线程,6项属性:string,string,integer,long,date + innerBean
org.springframework.beans.BeanUtils |
7.736 |
org.apache.commons.beanutils.PropertyUtils |
12618.541 |
org.apache.commons.beanutils.BeanUtils |
19641.511 |
org.springframework.cglib.beans.BeanCopier | 1.767 |
ma.glasnost.orika.impl.DefaultMapperFactory | 25.192 |
get,set | 0.013 |
测试5:20线程,21项属性:string5,integer5,long5,date5 + innerBean
org.springframework.beans.BeanUtils |
10.389 |
org.apache.commons.beanutils.PropertyUtils |
143.143 |
org.apache.commons.beanutils.BeanUtils |
239.994 |
org.springframework.cglib.beans.BeanCopier | 0.825 |
ma.glasnost.orika.impl.DefaultMapperFactory | 10.574 |
get,set | 0.005 |
测试6:50线程,21项属性:string5,integer5,long5,date5 + innerBean
测试7:20线程,41项属性:string10,integer10,long10,date10 + innerBean
测试8:50线程,41项属性:string10,integer10,long10,date10 + innerBean 见总表
org.springframework.beans.BeanUtils |
0.317 |
2.909 |
7.736 |
10.389 | 28.949 | 22.980 | 60.596 |
org.apache.commons.beanutils.PropertyUtils |
3.509 |
38.671 |
12618.541 |
143.143 | 23123 | / | / |
org.apache.commons.beanutils.BeanUtils | 5.996 |
63.438 |
19641.511 |
239.994 | 26611 | / | / |
org.springframework.cglib.beans.BeanCopier | 0.069 | 0.638 | 1.767 | 0.825 | 4.621 | 0.896 | 4.159 |
ma.glasnost.orika.impl.DefaultMapperFactory | 0.466 | 10.472 | 25.192 | 10.574 | 28.684 | 10.849 | 29.388 |
get,set | 0.001 | 0.005 | 0.013 | 0.005 | 0.013 | 0.005 | 0.013 |
结论:
直接用get,set是最快的,如果实例字段比较少的情况下,建议直接使用get,set。
cglib在没有converter的情况下性能是最好的,如果有converter的话要具体看converter的实现,总体速度也是很快的,建议不需要或简单converter场景的使用。
Spring beanUtils 和orika在并发低情况下速度相当,当线程增加以及字段量增加的时候,orika性能逐渐有优势(orika对于21属性和41属性速度差不多,而spring beanUtils由于是基于反射实现,属性增加耗时也相应增加)。orika劣势在于需要初始化Factory,每个类转换都需要注册到factory,使用起来比较繁琐,orika优势在于字段量大,并发高的场景,并且orika是深拷贝可以自己定义转换字段,所以推荐在需要的情况使用。
Apache的相对比较慢,不建议使用。
推荐大家使用cglib和orika