關(guān)于BeanUtils.copyProperties(source, target)的使用
BeanUtils.copyProperties
首先,使用的是org.springframework.beans.BeanUtils;
source 來源, target 目標(biāo)
顧名思義, BeanUtils.copyProperties(source, target); 第一個(gè)參數(shù)是需要拷貝的目標(biāo),第二個(gè)參數(shù)是拷貝后的目標(biāo)。
因?yàn)檫@個(gè)方法有很多種情況,容易分不清,所以今天測了一下不同情況下的結(jié)果如何。
1.target里面有source里沒有的屬性
并且此屬性有值時(shí)
2.target和source相同屬性的值不一樣時(shí)
下面是沒有拷貝之前的值

拷貝之后

可以看到,target里面不同值并沒有清空,而是保留了下來。而相中屬性本身存在的值被覆蓋。
3.當(dāng)target和source里面的屬性名相同而類型不同時(shí)

拷貝之后

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

