1. 程式人生 > 實用技巧 >Apache BeanUtils與Spring BeanUtils效能比較

Apache BeanUtils與Spring BeanUtils效能比較

在我們實際專案開發過程中,我們經常需要將不同的兩個物件例項進行屬性複製,從而基於源物件的屬性資訊進行後續操作,而不改變源物件的屬性資訊,比如DTO資料傳輸物件和資料物件DO,我們需要將DO物件進行屬性複製到DTO,但是物件格式又不一樣,所以我們需要編寫對映程式碼將物件中的屬性值從一種型別轉換成另一種型別。

物件拷貝

在具體介紹兩種 BeanUtils 之前,先來補充一些基礎知識。它們兩種工具本質上就是物件拷貝工具,而物件拷貝又分為深拷貝和淺拷貝,下面進行詳細解釋。

什麼是淺拷貝和深拷貝

在Java中,除了 基本資料型別之外,還存在 類的例項物件這個引用資料型別,而一般使用 “=”號做賦值操作的時候,對於基本資料型別,實際上是拷貝的它的值,但是對於物件而言,其實賦值的只是這個物件的引用,將原物件的引用傳遞過去,他們實際還是指向的同一個物件。而淺拷貝和深拷貝就是在這個基礎上做的區分,如果在拷貝這個物件的時候,只對基本資料型別進行了拷貝,而對引用資料型別只是進行引用的傳遞,而沒有真實的建立一個新的物件,則認為是淺拷貝。反之,在對引用資料型別進行拷貝的時候,建立了一個新的物件,並且複製其內的成員變數,則認為是深拷貝。

簡單來說:

  • 淺拷貝:對基本資料型別進行值傳遞,對引用資料型別進行引用傳遞般的拷貝,此為淺拷貝
  • 深拷貝:對基本資料型別進行值傳遞,對引用資料型別,建立一個新的物件,並複製其內容,此為深拷貝。

BeanUtils

前面簡單講了一下物件拷貝的一些知識,下面就來具體看下兩種 BeanUtils 工具

Apache 的 BeanUtils

首先來看一個非常簡單的BeanUtils的例子

publicclassPersonSource{
privateIntegerid;
privateStringusername;
privateStringpassword;
privateIntegerage;
//getters/settersomiited
}
publicclassPersonDest{
privateIntegerid;
privateStringusername;
privateIntegerage;
//getters/settersomiited
}
publicclassTestApacheBeanUtils{
publicstaticvoidmain(String[]args)throwsInvocationTargetException,IllegalAccessException{
//下面只是用於單獨測試
PersonSourcepersonSource=newPersonSource(1,"pjmike","12345",21);
PersonDestpersonDest=newPersonDest();
BeanUtils.copyProperties(personDest,personSource);
System.out.println("persondest:"+personDest);
}
}
persondest:PersonDest{id=1,username='pjmike',age=21}

從上面的例子可以看出,物件拷貝非常簡單,BeanUtils最常用的方法就是:

//將源物件中的值拷貝到目標物件//將源物件中的值拷貝到目標物件
publicstaticvoidcopyProperties(Objectdest,Objectorig)throwsIllegalAccessException,InvocationTargetException{
BeanUtilsBean.getInstance().copyProperties(dest,orig);
}

預設情況下,使用org.apache.commons.beanutils.BeanUtils對複雜物件的複製是引用,這是一種淺拷貝

關注公眾號程式設計師小樂回覆關鍵字“offer”獲取演算法面試題和答案。

但是由於 Apache下的BeanUtils物件拷貝效能太差,不建議使用,而且在阿里巴巴Java開發規約外掛上也明確指出:

Ali-Check | 避免用Apache Beanutils進行屬性的copy。

commons-beantutils 對於物件拷貝加了很多的檢驗,包括型別的轉換,甚至還會檢驗物件所屬的類的可訪問性,可謂相當複雜,這也造就了它的差勁的效能,具體實現程式碼如下:

publicvoidcopyProperties(finalObjectdest,finalObjectorig)
throwsIllegalAccessException,InvocationTargetException{

//Validateexistenceofthespecifiedbeans
if(dest==null){
thrownewIllegalArgumentException
("Nodestinationbeanspecified");
}
if(orig==null){
thrownewIllegalArgumentException("Nooriginbeanspecified");
}
if(log.isDebugEnabled()){
log.debug("BeanUtils.copyProperties("+dest+","+
orig+")");
}

//Copytheproperties,convertingasnecessary
if(originstanceofDynaBean){
finalDynaProperty[]origDescriptors=
((DynaBean)orig).getDynaClass().getDynaProperties();
for(DynaPropertyorigDescriptor:origDescriptors){
finalStringname=origDescriptor.getName();
//NeedtocheckisReadable()forWrapDynaBean
//(seeJiraissue#BEANUTILS-61)
if(getPropertyUtils().isReadable(orig,name)&&
getPropertyUtils().isWriteable(dest,name)){
finalObjectvalue=((DynaBean)orig).get(name);
copyProperty(dest,name,value);
}
}
}elseif(originstanceofMap){
@SuppressWarnings("unchecked")
final
//Mappropertiesarealwaysoftype<String,Object>
Map<String,Object>propMap=(Map<String,Object>)orig;
for(finalMap.Entry<String,Object>entry:propMap.entrySet()){
finalStringname=entry.getKey();
if(getPropertyUtils().isWriteable(dest,name)){
copyProperty(dest,name,entry.getValue());
}
}
}else/*if(origisastandardJavaBean)*/{
finalPropertyDescriptor[]origDescriptors=
getPropertyUtils().getPropertyDescriptors(orig);
for(PropertyDescriptororigDescriptor:origDescriptors){
finalStringname=origDescriptor.getName();
if("class".equals(name)){
continue;//Nopointintryingtosetanobject'sclass
}
if(getPropertyUtils().isReadable(orig,name)&&
getPropertyUtils().isWriteable(dest,name)){
try{
finalObjectvalue=
getPropertyUtils().getSimpleProperty(orig,name);
copyProperty(dest,name,value);
}catch(finalNoSuchMethodExceptione){
//Shouldnothappen
}
}
}
}

}

Spring 的 BeanUtils

使用spring的BeanUtils進行物件拷貝:

publicclassTestSpringBeanUtils{
publicstaticvoidmain(String[]args)throwsInvocationTargetException,IllegalAccessException{

//下面只是用於單獨測試
PersonSourcepersonSource=newPersonSource(1,"pjmike","12345",21);
PersonDestpersonDest=newPersonDest();
BeanUtils.copyProperties(personSource,personDest);
System.out.println("persondest:"+personDest);
}
}

Spring下的BeanUtils也是使用 copyProperties方法進行拷貝,只不過它的實現方式非常簡單,就是對兩個物件中相同名字的屬性進行簡單的get/set,僅檢查屬性的可訪問性。具體實現如下:

privatestaticvoidcopyProperties(Objectsource,Objecttarget,@NullableClass<?>editable,
@NullableString...ignoreProperties)throwsBeansException{

Assert.notNull(source,"Sourcemustnotbenull");
Assert.notNull(target,"Targetmustnotbenull");

Class<?>actualEditable=target.getClass();
if(editable!=null){
if(!editable.isInstance(target)){
thrownewIllegalArgumentException("Targetclass["+target.getClass().getName()+
"]notassignabletoEditableclass["+editable.getName()+"]");
}
actualEditable=editable;
}
PropertyDescriptor[]targetPds=getPropertyDescriptors(actualEditable);
List<String>ignoreList=(ignoreProperties!=null?Arrays.asList(ignoreProperties):null);

for(PropertyDescriptortargetPd:targetPds){
MethodwriteMethod=targetPd.getWriteMethod();
if(writeMethod!=null&&(ignoreList==null||!ignoreList.contains(targetPd.getName()))){
PropertyDescriptorsourcePd=getPropertyDescriptor(source.getClass(),targetPd.getName());
if(sourcePd!=null){
MethodreadMethod=sourcePd.getReadMethod();
if(readMethod!=null&&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0],readMethod.getReturnType())){
try{
if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())){
readMethod.setAccessible(true);
}
Objectvalue=readMethod.invoke(source);
if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())){
writeMethod.setAccessible(true);
}
writeMethod.invoke(target,value);
}
catch(Throwableex){
thrownewFatalBeanException(
"Couldnotcopyproperty'"+targetPd.getName()+"'fromsourcetotarget",ex);
}
}
}
}
}
}

可以看到,成員變數賦值是基於目標物件的成員列表,並且會跳過ignore的以及在源物件中不存在,所以這個方法是安全的,不會因為兩個物件之間的結構差異導致錯誤,但是必須保證同名的兩個成員變數型別相同。

小結

以上簡要的分析兩種BeanUtils,因為Apache下的BeanUtils效能較差,不建議使用,可以使用 Spring的BeanUtils ,或者使用其他拷貝框架,比如:Dozer、ModelMapper