關于BeanUtils.copyProperties(source, target)的使用
BeanUtils.copyProperties
首先,使用的是org.springframework.beans.BeanUtils;
source 來源, target 目標
顧名思義, BeanUtils.copyProperties(source, target); 第一個參數(shù)是需要拷貝的目標,第二個參數(shù)是拷貝后的目標。
因為這個方法有很多種情況,容易分不清,所以今天測了一下不同情況下的結(jié)果如何。
1.target里面有source里沒有的屬性
并且此屬性有值時
2.target和source相同屬性的值不一樣時
下面是沒有拷貝之前的值
拷貝之后
可以看到,target里面不同值并沒有清空,而是保留了下來。而相中屬性本身存在的值被覆蓋。
3.當target和source里面的屬性名相同而類型不同時
拷貝之后
類型不同的屬性無法拷貝。
Spring自帶BeanUtils.copyProperties(Object source, Object target)之坑
在java服務化項目中,客戶端和服務端之間交互經(jīng)常用到BeanCopy,其目的是為了方便類之間的賦值,簡單方便,但是經(jīng)常會遇到復合對象賦值不上去的情況,究其原因是對BeanUtils.copyProperties(Object source, Object target)方法底層源碼的不了解導致的,下面我來一步一步解釋其原因。
先看一個例子:
@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); } }
當我們用BeanUtils.copyProperties(monthNewStoreTopResponse , bdmTeamMonthNewStoreTopResult )時會發(fā)現(xiàn)myTopInfo這個對象賦值為null,這是為什么呢?讓我們來看一看源碼:
//這是點進源碼的第一段代碼 public static void copyProperties(Object source, Object target) throws BeansException { copyProperties(source, target, null, (String[]) null); } //這個才是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())這個校驗起到了關鍵的作用,我們再進入這段代碼的源碼看一眼,源碼如下:
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 對象所表示的類或接口與指定的 Class 參數(shù)所表示的類或接口是否相同,或是否是其超類或超接口。
如果是則返回 true;否則返回 false。
如果該 Class表示一個基本類型,且指定的 Class 參數(shù)正是該 Class 對象,則該方法返回 true;否則返回 false。
意思其實就是說lhsType是不是rhsType的子類,如果是,則返回true,否則返回false。
這也就是說我們上面的例子MonthNewStoreTopMyInfo 對象和我們將要賦值的對象BdmTeamMonthNewStoreTopMyInfo 是同一個對象或者是它的子類才可以copy賦值成功,否則直接跳過返回了,不進行writeMethod.invoke(target, value)賦值;
哪為什么topInfoList卻能賦值成功呢?
因為在lhsType.isAssignableFrom(rhsType)校驗的時候是判斷的是List類型的子類而不是List<BdmTeamMonthNewStoreTopMyInfo>中的BdmTeamMonthNewStoreTopMyInfo的子類。
所以我們在用Spring自帶BeanUtils.copyProperties(Object source, Object target)進行對象copy時候需要特別注意,如果變量為非java自帶的對象類型,則需要注意復合對象中的變量對象和被拷貝變量對象是同類型才可以。
如果MonthNewStoreTopMyInfo 和BdmTeamMonthNewStoreTopMyInfo不是同一個類型,可以通過先獲得MonthNewStoreTopMyInfo 這個對象再和需要賦值的對象BdmTeamMonthNewStoreTopMyInfo進行變量級別的調(diào)用BeanUtils.copyProperties(MonthNewStoreTopMyInfo , BdmTeamMonthNewStoreTopMyInfo),最后再把復制后的結(jié)果set進結(jié)果集。
最好的解決辦法是創(chuàng)建一個公共的model對象,替換MonthNewStoreTopMyInfo和BdmTeamMonthNewStoreTopMyInfo,這樣也少創(chuàng)建了一個類,同時也減少了代碼量,維護一份model,當有新增需求變化時,只需要修改公共的model對象即可,簡單方便。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
springboot業(yè)務功能實戰(zhàn)之告別輪詢websocket的集成使用
WebSocket使得客戶端和服務器之間的數(shù)據(jù)交換變得更加簡單,允許服務端主動向客戶端推送數(shù)據(jù),下面這篇文章主要給大家介紹了關于springboot業(yè)務功能實戰(zhàn)之告別輪詢websocket的集成使用,需要的朋友可以參考下2022-10-10關于eclipse安裝spring插件報錯An error occurred while collecting item
這篇文章主要介紹了關于eclipse安裝spring插件報錯An error occurred while collecting items to be installed...解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-08-08SpringBoot使用AOP統(tǒng)一日志管理的方法詳解
這篇文章主要為大家分享一個干貨:超簡潔SpringBoot使用AOP統(tǒng)一日志管理,文中的示例代碼講解詳細,感興趣的小伙伴快跟隨小編一起學習學習吧2022-05-05解決springboot?druid數(shù)據(jù)庫連接池連接失敗后一直重連問題
這篇文章主要介紹了解決springboot?druid數(shù)據(jù)庫連接池連接失敗后一直重連問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11解決redisTemplate向redis中插入String類型數(shù)據(jù)時出現(xiàn)亂碼問題
這篇文章主要介紹了解決redisTemplate向redis中插入String類型數(shù)據(jù)時出現(xiàn)亂碼問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12java 使用線程監(jiān)控文件目錄變化的實現(xiàn)方法
這篇文章主要介紹了java 使用線程監(jiān)控文件目錄變化的實現(xiàn)方法的相關資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-10-10Springboot實現(xiàn)Java阿里短信發(fā)送代碼實例
這篇文章主要介紹了springboot實現(xiàn)Java阿里短信發(fā)送代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-02-02