關(guān)于BeanUtils.copyProperties(source, target)的使用
BeanUtils.copyProperties
首先,使用的是org.springframework.beans.BeanUtils;
source 來(lái)源, target 目標(biāo)
顧名思義, BeanUtils.copyProperties(source, target); 第一個(gè)參數(shù)是需要拷貝的目標(biāo),第二個(gè)參數(shù)是拷貝后的目標(biāo)。
因?yàn)檫@個(gè)方法有很多種情況,容易分不清,所以今天測(cè)了一下不同情況下的結(jié)果如何。
1.target里面有source里沒有的屬性
并且此屬性有值時(shí)
2.target和source相同屬性的值不一樣時(shí)
下面是沒有拷貝之前的值
拷貝之后
可以看到,target里面不同值并沒有清空,而是保留了下來(lái)。而相中屬性本身存在的值被覆蓋。
3.當(dāng)target和source里面的屬性名相同而類型不同時(shí)
拷貝之后
類型不同的屬性無(wú)法拷貝。
Spring自帶BeanUtils.copyProperties(Object source, Object target)之坑
在java服務(wù)化項(xiàng)目中,客戶端和服務(wù)端之間交互經(jīng)常用到BeanCopy,其目的是為了方便類之間的賦值,簡(jiǎn)單方便,但是經(jīng)常會(huì)遇到復(fù)合對(duì)象賦值不上去的情況,究其原因是對(duì)BeanUtils.copyProperties(Object source, Object target)方法底層源碼的不了解導(dǎo)致的,下面我來(lái)一步一步解釋其原因。
先看一個(gè)例子:
@Data public class BdmTeamMonthNewStoreTopResult implements Serializable { private static final long serialVersionUID = -3251482519506276368L; /** * 排名列表 */ private List<BdmTeamMonthNewStoreTopInfo> topInfoList; /** * 我的排名信息 */ private BdmTeamMonthNewStoreTopMyInfo myTopInfo; @Override public String toString() { return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } @Data public class MonthNewStoreTopInfoResponse implements Serializable { private static final long serialVersionUID = 4483822161951780674L; /** * 排名信息列表 */ private List<MonthNewStoreTopInfo> topInfoList; /** * 我的排名信息 */ private MonthNewStoreTopMyInfo myTopInfo; @Override public String toString() { return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } }
當(dāng)我們用BeanUtils.copyProperties(monthNewStoreTopResponse , bdmTeamMonthNewStoreTopResult )時(shí)會(huì)發(fā)現(xiàn)myTopInfo這個(gè)對(duì)象賦值為null,這是為什么呢?讓我們來(lái)看一看源碼:
//這是點(diǎn)進(jìn)源碼的第一段代碼 public static void copyProperties(Object source, Object target) throws BeansException { copyProperties(source, target, null, (String[]) null); } //這個(gè)才是copy的主代碼 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); } } } } } }
其中ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())這個(gè)校驗(yàn)起到了關(guān)鍵的作用,我們?cè)龠M(jìn)入這段代碼的源碼看一眼,源碼如下:
public static boolean isAssignable(Class<?> lhsType, Class<?> rhsType) { Assert.notNull(lhsType, "Left-hand side type must not be null"); Assert.notNull(rhsType, "Right-hand side type must not be null"); if (lhsType.isAssignableFrom(rhsType)) { return true; } if (lhsType.isPrimitive()) { Class<?> resolvedPrimitive = primitiveWrapperTypeMap.get(rhsType); if (lhsType == resolvedPrimitive) { return true; } } else { Class<?> resolvedWrapper = primitiveTypeToWrapperMap.get(rhsType); if (resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper)) { return true; } } return false; }
其中l(wèi)hsType.isAssignableFrom(rhsType)判定此 Class 對(duì)象所表示的類或接口與指定的 Class 參數(shù)所表示的類或接口是否相同,或是否是其超類或超接口。
如果是則返回 true;否則返回 false。
如果該 Class表示一個(gè)基本類型,且指定的 Class 參數(shù)正是該 Class 對(duì)象,則該方法返回 true;否則返回 false。
意思其實(shí)就是說(shuō)lhsType是不是rhsType的子類,如果是,則返回true,否則返回false。
這也就是說(shuō)我們上面的例子MonthNewStoreTopMyInfo 對(duì)象和我們將要賦值的對(duì)象BdmTeamMonthNewStoreTopMyInfo 是同一個(gè)對(duì)象或者是它的子類才可以copy賦值成功,否則直接跳過(guò)返回了,不進(jìn)行writeMethod.invoke(target, value)賦值;
哪為什么topInfoList卻能賦值成功呢?
因?yàn)樵趌hsType.isAssignableFrom(rhsType)校驗(yàn)的時(shí)候是判斷的是List類型的子類而不是List<BdmTeamMonthNewStoreTopMyInfo>中的BdmTeamMonthNewStoreTopMyInfo的子類。
所以我們?cè)谟肧pring自帶BeanUtils.copyProperties(Object source, Object target)進(jìn)行對(duì)象copy時(shí)候需要特別注意,如果變量為非java自帶的對(duì)象類型,則需要注意復(fù)合對(duì)象中的變量對(duì)象和被拷貝變量對(duì)象是同類型才可以。
如果MonthNewStoreTopMyInfo 和BdmTeamMonthNewStoreTopMyInfo不是同一個(gè)類型,可以通過(guò)先獲得MonthNewStoreTopMyInfo 這個(gè)對(duì)象再和需要賦值的對(duì)象BdmTeamMonthNewStoreTopMyInfo進(jìn)行變量級(jí)別的調(diào)用BeanUtils.copyProperties(MonthNewStoreTopMyInfo , BdmTeamMonthNewStoreTopMyInfo),最后再把復(fù)制后的結(jié)果set進(jìn)結(jié)果集。
最好的解決辦法是創(chuàng)建一個(gè)公共的model對(duì)象,替換MonthNewStoreTopMyInfo和BdmTeamMonthNewStoreTopMyInfo,這樣也少創(chuàng)建了一個(gè)類,同時(shí)也減少了代碼量,維護(hù)一份model,當(dāng)有新增需求變化時(shí),只需要修改公共的model對(duì)象即可,簡(jiǎn)單方便。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- BeanUtils.copyProperties()參數(shù)的賦值順序說(shuō)明
- BeanUtils.copyProperties使用總結(jié)以及注意事項(xiàng)說(shuō)明
- BeanUtils.copyProperties復(fù)制屬性失敗的原因及解決方案
- 解決BeanUtils.copyProperties之大坑
- Java BeanUtils.copyProperties的詳解
- BeanUtils.copyProperties擴(kuò)展--實(shí)現(xiàn)String轉(zhuǎn)Date
- 基于Beanutils.copyProperties()的用法及重寫提高效率
相關(guān)文章
springboot業(yè)務(wù)功能實(shí)戰(zhàn)之告別輪詢websocket的集成使用
WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于springboot業(yè)務(wù)功能實(shí)戰(zhàn)之告別輪詢websocket的集成使用,需要的朋友可以參考下2022-10-10關(guān)于eclipse安裝spring插件報(bào)錯(cuò)An error occurred while collecting item
這篇文章主要介紹了關(guān)于eclipse安裝spring插件報(bào)錯(cuò)An error occurred while collecting items to be installed...解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08SpringBoot使用AOP統(tǒng)一日志管理的方法詳解
這篇文章主要為大家分享一個(gè)干貨:超簡(jiǎn)潔SpringBoot使用AOP統(tǒng)一日志管理,文中的示例代碼講解詳細(xì),感興趣的小伙伴快跟隨小編一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05解決springboot?druid數(shù)據(jù)庫(kù)連接池連接失敗后一直重連問題
這篇文章主要介紹了解決springboot?druid數(shù)據(jù)庫(kù)連接池連接失敗后一直重連問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11解決redisTemplate向redis中插入String類型數(shù)據(jù)時(shí)出現(xiàn)亂碼問題
這篇文章主要介紹了解決redisTemplate向redis中插入String類型數(shù)據(jù)時(shí)出現(xiàn)亂碼問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12java 使用線程監(jiān)控文件目錄變化的實(shí)現(xiàn)方法
這篇文章主要介紹了java 使用線程監(jiān)控文件目錄變化的實(shí)現(xiàn)方法的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-10-10Spring boot熱部署devtools過(guò)程解析
這篇文章主要介紹了Spring boot熱部署devtools過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07Springboot實(shí)現(xiàn)Java阿里短信發(fā)送代碼實(shí)例
這篇文章主要介紹了springboot實(shí)現(xiàn)Java阿里短信發(fā)送代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02