javascript
两难!到底用 Spring BeanUtils 还是 Apache BeanUtils?
前言
在我們實(shí)際項(xiàng)目開發(fā)過程中,我們經(jīng)常需要將不同的兩個(gè)對(duì)象實(shí)例進(jìn)行屬性復(fù)制,從而基于源對(duì)象的屬性信息進(jìn)行后續(xù)操作,而不改變?cè)磳?duì)象的屬性信息,比如DTO數(shù)據(jù)傳輸對(duì)象和數(shù)據(jù)對(duì)象DO,我們需要將DO對(duì)象進(jìn)行屬性復(fù)制到DTO,但是對(duì)象格式又不一樣,所以我們需要編寫映射代碼將對(duì)象中的屬性值從一種類型轉(zhuǎn)換成另一種類型。
這種轉(zhuǎn)換最原始的方式就是手動(dòng)編寫大量的?get/set代碼,當(dāng)然這是我們開發(fā)過程不愿意去做的,因?yàn)樗_實(shí)顯得很繁瑣。為了解決這一痛點(diǎn),就誕生了一些方便的類庫(kù),常用的有 apache的?BeanUtils,spring的?BeanUtils,?Dozer,Orika等拷貝工具。這篇文章主要介紹 Apache的BeanUtils 與 Spring 的BeanUtils,其他框架后續(xù)文章再做介紹
對(duì)象拷貝
在具體介紹兩種 BeanUtils之前,先來補(bǔ)充一些基礎(chǔ)知識(shí)。它們兩種工具本質(zhì)上就是對(duì)象拷貝工具,而對(duì)象拷貝又分為深拷貝和淺拷貝,下面進(jìn)行詳細(xì)解釋。
什么是淺拷貝和深拷貝
在Java中,除了?基本數(shù)據(jù)類型之外,還存在?類的實(shí)例對(duì)象這個(gè)引用數(shù)據(jù)類型,而一般使用 “=”號(hào)做賦值操作的時(shí)候,對(duì)于基本數(shù)據(jù)類型,實(shí)際上是拷貝的它的值,但是對(duì)于對(duì)象而言,其實(shí)賦值的只是這個(gè)對(duì)象的引用,將原對(duì)象的引用傳遞過去,他們實(shí)際還是指向的同一個(gè)對(duì)象。
而淺拷貝和深拷貝就是在這個(gè)基礎(chǔ)上做的區(qū)分,如果在拷貝這個(gè)對(duì)象的時(shí)候,只對(duì)基本數(shù)據(jù)類型進(jìn)行了拷貝,而對(duì)引用數(shù)據(jù)類型只是進(jìn)行引用的傳遞,而沒有真實(shí)的創(chuàng)建一個(gè)新的對(duì)象,則認(rèn)為是淺拷貝。反之,在對(duì)引用數(shù)據(jù)類型進(jìn)行拷貝的時(shí)候,創(chuàng)建了一個(gè)新的對(duì)象,并且復(fù)制其內(nèi)的成員變量,則認(rèn)為是深拷貝。
簡(jiǎn)單來說:
-
淺拷貝:對(duì)基本數(shù)據(jù)類型進(jìn)行值傳遞,對(duì)引用數(shù)據(jù)類型進(jìn)行引用傳遞般的拷貝,此為淺拷貝
-
深拷貝:對(duì)基本數(shù)據(jù)類型進(jìn)行值傳遞,對(duì)引用數(shù)據(jù)類型,創(chuàng)建一個(gè)新的對(duì)象,并復(fù)制其內(nèi)容,此為深拷貝。
BeanUtils
前面簡(jiǎn)單講了一下對(duì)象拷貝的一些知識(shí),下面就來具體看下兩種BeanUtils工具
apache 的 BeanUtils
首先來看一個(gè)非常簡(jiǎn)單的BeanUtils的例子
public?class?PersonSource??{private?Integer?id;private?String?username;private?String?password;private?Integer?age;//?getters/setters?omiited } public?class?PersonDest?{private?Integer?id;private?String?username;private?Integer?age;//?getters/setters?omiited } public?class?TestApacheBeanUtils?{public?static?void?main(String[]?args)?throws?InvocationTargetException,?IllegalAccessException?{//下面只是用于單獨(dú)測(cè)試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}從上面的例子可以看出,對(duì)象拷貝非常簡(jiǎn)單,BeanUtils最常用的方法就是:
//將源對(duì)象中的值拷貝到目標(biāo)對(duì)象 public?static?void?copyProperties(Object?dest,?Object?orig)?throws?IllegalAccessException,?InvocationTargetException?{BeanUtilsBean.getInstance().copyProperties(dest,?orig); }默認(rèn)情況下,使用org.apache.commons.beanutils.BeanUtils對(duì)復(fù)雜對(duì)象的復(fù)制是引用,這是一種淺拷貝
但是由于 Apache下的BeanUtils對(duì)象拷貝性能太差,不建議使用,而且在阿里巴巴Java開發(fā)規(guī)約插件上也明確指出:
“Ali-Check | 避免用Apache Beanutils進(jìn)行屬性的copy。
commons-beantutils 對(duì)于對(duì)象拷貝加了很多的檢驗(yàn),包括類型的轉(zhuǎn)換,甚至還會(huì)檢驗(yàn)對(duì)象所屬的類的可訪問性,可謂相當(dāng)復(fù)雜,這也造就了它的差勁的性能,具體實(shí)現(xiàn)代碼如下:
public?void?copyProperties(final?Object?dest,?final?Object?orig)throws?IllegalAccessException,?InvocationTargetException?{//?Validate?existence?of?the?specified?beansif?(dest?==?null)?{throw?new?IllegalArgumentException("No?destination?bean?specified");}if?(orig?==?null)?{throw?new?IllegalArgumentException("No?origin?bean?specified");}if?(log.isDebugEnabled())?{log.debug("BeanUtils.copyProperties("?+?dest?+?",?"?+orig?+?")");}//?Copy?the?properties,?converting?as?necessaryif?(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);}}}?else?if?(orig?instanceof?Map)?{@SuppressWarnings("unchecked")final//?Map?properties?are?always?of?type?<String,?Object>Map<String,?Object>?propMap?=?(Map<String,?Object>)?orig;for?(final?Map.Entry<String,?Object>?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進(jìn)行對(duì)象拷貝:
public?class?TestSpringBeanUtils?{public?static?void?main(String[]?args)?throws?InvocationTargetException,?IllegalAccessException?{//下面只是用于單獨(dú)測(cè)試PersonSource?personSource?=?new?PersonSource(1,?"pjmike",?"12345",?21);PersonDest?personDest?=?new?PersonDest();BeanUtils.copyProperties(personSource,personDest);System.out.println("persondest:?"+personDest);} }spring下的BeanUtils也是使用?copyProperties方法進(jìn)行拷貝,只不過它的實(shí)現(xiàn)方式非常簡(jiǎn)單,就是對(duì)兩個(gè)對(duì)象中相同名字的屬性進(jìn)行簡(jiǎn)單的get/set,僅檢查屬性的可訪問性。具體實(shí)現(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<String>?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)對(duì)象的成員列表,并且會(huì)跳過ignore的以及在源對(duì)象中不存在,所以這個(gè)方法是安全的,不會(huì)因?yàn)閮蓚€(gè)對(duì)象之間的結(jié)構(gòu)差異導(dǎo)致錯(cuò)誤,但是必須保證同名的兩個(gè)成員變量類型相同
小結(jié)
以上簡(jiǎn)要的分析兩種BeanUtils,因?yàn)锳pache下的BeanUtils性能較差,不建議使用,可以使用 Spring的BeanUtils,或者使用其他拷貝框架,比如 cglib BeanCopier,基于javassist的Orika等,這些也是非常優(yōu)秀的類庫(kù),值得去嘗試,并且也有人去評(píng)測(cè)過這些Bean映射工具,具體分析請(qǐng)參閱: 常見Bean映射工具分析評(píng)測(cè)及Orika介紹
總結(jié)
以上是生活随笔為你收集整理的两难!到底用 Spring BeanUtils 还是 Apache BeanUtils?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 拥抱 Java 8 并行流吧,速度飞起!
- 下一篇: 统计学出身但编程一般,该选择数据分析还是