兩難!到底用Apache BeanUtils還是Spring BeanUtils?
來源 |?https://urlify.cn/vUfIry
在我們實際項目開發(fā)過程中,我們經(jīng)常需要將不同的兩個對象實例進行屬性復(fù)制,從而基于源對象的屬性信息進行后續(xù)操作,而不改變源對象的屬性信息, 比如 DTO 數(shù)據(jù)傳輸對象和數(shù)據(jù)對象 DO,我們需要將 DO 對象進行屬性復(fù)制到 DTO,但是對象格式又不一樣,所以我們需要編寫映射代碼將對象中的屬性值從一種類型轉(zhuǎn)換成另一種類型。
對象拷貝
在具體介紹兩種 BeanUtils 之前,先來補充一些基礎(chǔ)知識。它們兩種工具本質(zhì)上就是對象拷貝工具,而對象拷貝又分為深拷貝和淺拷貝,下面進行詳細解釋。
什么是淺拷貝和深拷貝
在 Java 中,除了 基本數(shù)據(jù)類型之外,還存在 類的實例對象這個引用數(shù)據(jù)類型,而一般使用 “=” 號做賦值操作的時候,對于基本數(shù)據(jù)類型,實際上是拷貝的它的值,但是對于對象而言,其實賦值的只是這個對象的引用,將原對象的引用傳遞過去,他們實際還是指向的同一個對象。而淺拷貝和深拷貝就是在這個基礎(chǔ)上做的區(qū)分,如果在拷貝這個對象的時候,只對基本數(shù)據(jù)類型進行了拷貝,而對引用數(shù)據(jù)類型只是進行引用的傳遞,而沒有真實的創(chuàng)建一個新的對象,則認(rèn)為是淺拷貝。反之,在對引用數(shù)據(jù)類型進行拷貝的時候,創(chuàng)建了一個新的對象,并且復(fù)制其內(nèi)的成員變量,則認(rèn)為是深拷貝。
粉絲福利:銷售過億的《Effective Java 第三版》最新中文版開放下載!升職加薪就靠它了!
簡單來說:
淺拷貝:對基本數(shù)據(jù)類型進行值傳遞,對引用數(shù)據(jù)類型進行引用傳遞般的拷貝,此為淺拷貝
深拷貝:對基本數(shù)據(jù)類型進行值傳遞,對引用數(shù)據(jù)類型,創(chuàng)建一個新的對象,并復(fù)制其內(nèi)容,此為深拷貝。

BeanUtils
前面簡單講了一下對象拷貝的一些知識,下面就來具體看下兩種 BeanUtils 工具
Apache 的 BeanUtils
首先來看一個非常簡單的 BeanUtils 的例子
publicclass?PersonSource?{
????private?Integer?id;
????private?String?username;
????private?String?password;
????private?Integer?age;
????//?getters/setters?omiited
}
publicclass?PersonDest?{
????private?Integer?id;
????private?String?username;
????private?Integer?age;
????//?getters/setters?omiited
}
publicclass?TestApacheBeanUtils?{
????public?static?void?main(String[]?args)?throws?InvocationTargetException,?IllegalAccessException?{
???????//下面只是用于單獨測試
????????PersonSource?personSource?=?new?PersonSource(1,?"pjmike",?"12345",?21);
????????PersonDest?personDest?=?new?PersonDest();
????????BeanUtils.copyProperties(personDest,personSource);
????????System.out.println("persondest:?"+personDest);
????}
}
persondest:?PersonDest{id=1,?username='pjmike',?age=21}
從上面的例子可以看出,對象拷貝非常簡單,BeanUtils 最常用的方法就是:
//將源對象中的值拷貝到目標(biāo)對象//將源對象中的值拷貝到目標(biāo)對象
public?static?void?copyProperties(Object?dest,?Object?orig)?throws?IllegalAccessException,?InvocationTargetException?{
????BeanUtilsBean.getInstance().copyProperties(dest,?orig);
}
默認(rèn)情況下,使用 org.apache.commons.beanutils.BeanUtils 對復(fù)雜對象的復(fù)制是引用,這是一種淺拷貝
但是由于 Apache 下的 BeanUtils 對象拷貝性能太差,不建議使用,而且在阿里巴巴 Java 開發(fā)規(guī)約插件上也明確指出:
Ali-Check | 避免用 Apache Beanutils 進行屬性的 copy。
commons-beantutils 對于對象拷貝加了很多的檢驗,包括類型的轉(zhuǎn)換,甚至還會檢驗對象所屬的類的可訪問性, 可謂相當(dāng)復(fù)雜,這也造就了它的差勁的性能,具體實現(xiàn)代碼如下:
public?void?copyProperties(final?Object?dest,?final?Object?orig)
????????throws?IllegalAccessException,?InvocationTargetException?{
????????//?Validate?existence?of?the?specified?beans
????????if?(dest?==?null)?{
????????????thrownew?IllegalArgumentException
????????????????????("No?destination?bean?specified");
????????}
????????if?(orig?==?null)?{
????????????thrownew?IllegalArgumentException("No?origin?bean?specified");
????????}
????????if?(log.isDebugEnabled())?{
????????????log.debug("BeanUtils.copyProperties("?+?dest?+?",?"?+
??????????????????????orig?+?")");
????????}
????????//?Copy?the?properties,?converting?as?necessary
????????if?(orig?instanceof?DynaBean)?{
????????????final?DynaProperty[]?origDescriptors?=
????????????????((DynaBean)?orig).getDynaClass().getDynaProperties();
????????????for?(DynaProperty?origDescriptor?:?origDescriptors)?{
????????????????final?String?name?=?origDescriptor.getName();
????????????????//?Need?to?check?isReadable()?for?WrapDynaBean
????????????????//?(see?Jira?issue#?BEANUTILS-61)
????????????????if?(getPropertyUtils().isReadable(orig,?name)?&&
????????????????????getPropertyUtils().isWriteable(dest,?name))?{
????????????????????final?Object?value?=?((DynaBean)?orig).get(name);
????????????????????copyProperty(dest,?name,?value);
????????????????}
????????????}
????????}?elseif?(orig?instanceof?Map)?{
????????????@SuppressWarnings("unchecked")
????????????final
????????????//?Map?properties?are?always?of?type?
????????????Map?propMap?=?(Map)?orig;
????????????for?(final?Map.Entry?entry?:?propMap.entrySet())?{
????????????????final?String?name?=?entry.getKey();
????????????????if?(getPropertyUtils().isWriteable(dest,?name))?{
????????????????????copyProperty(dest,?name,?entry.getValue());
????????????????}
????????????}
????????}?else/*?if?(orig?is?a?standard?JavaBean)?*/?{
????????????final?PropertyDescriptor[]?origDescriptors?=
????????????????getPropertyUtils().getPropertyDescriptors(orig);
????????????for?(PropertyDescriptor?origDescriptor?:?origDescriptors)?{
????????????????final?String?name?=?origDescriptor.getName();
????????????????if?("class".equals(name))?{
????????????????????continue;?//?No?point?in?trying?to?set?an?object's?class
????????????????}
????????????????if?(getPropertyUtils().isReadable(orig,?name)?&&
????????????????????getPropertyUtils().isWriteable(dest,?name))?{
????????????????????try?{
????????????????????????final?Object?value?=
????????????????????????????getPropertyUtils().getSimpleProperty(orig,?name);
????????????????????????copyProperty(dest,?name,?value);
????????????????????}?catch?(final?NoSuchMethodException?e)?{
????????????????????????//?Should?not?happen
????????????????????}
????????????????}
????????????}
????????}
????}
Spring 的 BeanUtils
使用 spring 的 BeanUtils 進行對象拷貝:
publicclass?TestSpringBeanUtils?{
????public?static?void?main(String[]?args)?throws?InvocationTargetException,?IllegalAccessException?{
???????//下面只是用于單獨測試
????????PersonSource?personSource?=?new?PersonSource(1,?"pjmike",?"12345",?21);
????????PersonDest?personDest?=?new?PersonDest();
????????BeanUtils.copyProperties(personSource,personDest);
????????System.out.println("persondest:?"+personDest);
????}
}
Spring 下的 BeanUtils 也是使用 copyProperties 方法進行拷貝,只不過它的實現(xiàn)方式非常簡單,就是對兩個對象中相同名字的屬性進行簡單的 get/set,僅檢查屬性的可訪問性。具體實現(xiàn)如下:
private?static?void?copyProperties(Object?source,?Object?target,?@Nullable?Class>?editable,
??????@Nullable?String...?ignoreProperties)?throws?BeansException?{
????Assert.notNull(source,?"Source?must?not?be?null");
????Assert.notNull(target,?"Target?must?not?be?null");
????Class>?actualEditable?=?target.getClass();
????if?(editable?!=?null)?{
??????if?(!editable.isInstance(target))?{
????????throw?new?IllegalArgumentException("Target?class?["?+?target.getClass().getName()?+
????????????"]?not?assignable?to?Editable?class?["?+?editable.getName()?+?"]");
??????}
??????actualEditable?=?editable;
????}
????PropertyDescriptor[]?targetPds?=?getPropertyDescriptors(actualEditable);
????List?ignoreList?=?(ignoreProperties?!=?null???Arrays.asList(ignoreProperties)?:?null);
????for?(PropertyDescriptor?targetPd?:?targetPds)?{
??????Method?writeMethod?=?targetPd.getWriteMethod();
??????if?(writeMethod?!=?null?&&?(ignoreList?==?null?||?!ignoreList.contains(targetPd.getName())))?{
????????PropertyDescriptor?sourcePd?=?getPropertyDescriptor(source.getClass(),?targetPd.getName());
????????if?(sourcePd?!=?null)?{
??????????Method?readMethod?=?sourcePd.getReadMethod();
??????????if?(readMethod?!=?null?&&
??????????????ClassUtils.isAssignable(writeMethod.getParameterTypes()[0],?readMethod.getReturnType()))?{
????????????try?{
??????????????if?(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()))?{
????????????????readMethod.setAccessible(true);
??????????????}
??????????????Object?value?=?readMethod.invoke(source);
??????????????if?(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()))?{
????????????????writeMethod.setAccessible(true);
??????????????}
??????????????writeMethod.invoke(target,?value);
????????????}
????????????catch?(Throwable?ex)?{
??????????????throw?new?FatalBeanException(
??????????????????"Could?not?copy?property?'"?+?targetPd.getName()?+?"'?from?source?to?target",?ex);
????????????}
??????????}
????????}
??????}
????}
??}
可以看到,成員變量賦值是基于目標(biāo)對象的成員列表,并且會跳過 ignore 的以及在源對象中不存在,所以這個方法是安全的,不會因為兩個對象之間的結(jié)構(gòu)差異導(dǎo)致錯誤,但是必須保證同名的兩個成員變量類型相同
小結(jié)
以上簡要的分析兩種 BeanUtils,因為 Apache 下的 BeanUtils 性能較差,不建議使用,可以使用 Spring 的 BeanUtils ,或者使用其他拷貝框架,比如:Dozer、ModelMapper 等等
推薦閱讀
