利用反射和註解,拷貝型別相同,屬性名不同的物件
阿新 • • 發佈:2021-09-03
利用反射和註解,拷貝型別相同,屬性名不同的物件
轉載自:https://segmentfault.com/a/1190000018623737?sort=votes
1、前言
最近開發遇到一個問題,兩個物件進行屬性值拷貝。理論上來說可以直接藉助org.springframework.beans.BeanUtils.copyProperties(Object source, Object target)來進行拷貝,奈何兩個物件屬性名不同,懵逼臉。
2、問題引入
待拷貝類
/** * @author : uu * @version v1.0 * @Description: 源User */ public class OriginUser { /**id*/ private Long originId; /**名稱*/ private String originName; /**密碼*/ private String password; /**出生日期*/ private Date originBirthDay; /**是否健康*/ private Boolean originHealth; /**getter/setter省略*/ }
目標類
/** * @author : uu * @version v1.0 * @Description: 目標User */ public class TargetUser { /**id*/ private Long targetId; /**名稱*/ private String targetName; /**密碼*/ private String password; /**出生日期*/ private Date targetBirthDay; /**是否健康*/ private Boolean targetHealth; /**getter/setter省略*/ }
拷貝上述兩個類產生的物件,spring為我們提供的工具類就直接歇菜了。最初想到的方案便是targetUser.setXxx(originUser.getXxx()),這種方式簡單粗暴,易寫,不易擴充套件。如果屬性過多的時候,寫到吐血。
3、問題思考
物件的拷貝,我們可以使用反射進行處理,但是兩個不同屬性的物件進行拷貝的問題在於,我們如何讓兩個不同的屬性名進行關聯。順著這個思路,我開始考慮設定一個工具類專門存放兩個物件的屬性對應關係。這個時候問題又出現了,如果有成千上萬的物件,建立關係對映又是浩大的工程。
偶然間想到fastJson中利用@JSONField(name="xxx")註解可以給屬性設定別名,那麼在拷貝不同屬性物件時,我們也可以使用這種方案。
4、程式碼開發
4.1 CopyField註解
/**
* 該註解應用於類屬性上,主要為了設定屬性別名,適用於不同屬性拷貝
* @author : uu
* @version v1.0
* @Description: 常用bean相關方法
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CopyField {
/**
* 在即將被拷貝的屬性上面,設定目標屬性名
* @return
*/
String targetName() default "";
/**
* 在即將拷貝至改屬性上面,設定源屬性名
* @return
*/
String originName() default "";
}
4.2 bean改造
- 註解中設定了兩個方法,為了縮小篇幅,我會在原對類和目標類中使用同一個註解,建議實踐中分離為兩個註解。
待拷貝bean
public class OriginUser {
/**id*/
@CopyField(targetName = "targetId")
private Long originId;
/**名稱*/
@CopyField(targetName = "targetName")
private String originName;
/**密碼*/
private String password;
/**出生日期*/
private Date originBirthDay;
/**是否健康*/
private Boolean originHealth;
}
目標bean
public class TargetUser {
/**id*/
private Long targetId;
/**名稱*/
private String targetName;
/**密碼*/
private String password;
/**出生日期*/
@CopyField(originName = "originBirthDay")
private Date targetBirthDay;
/**是否健康*/
@CopyField(originName = "originHealth")
private Boolean targetHealth;
}
4.3 BeanUtil工具類
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author : uu
* @version v1.0
* @Description: 常用bean相關方法
*/
public class BeanUtils {
private static Logger logger = LoggerFactory.getLogger(BeanUtils.class);
/**
* <h3>拷貝一個物件的屬性至另一個物件</h3>
* <p>
* 支援兩個物件之間不同屬性名稱進行拷貝,使用註解{@link CopyField}
* </p>
* @param originBean 源物件 使用註解{@link CopyField#targetName()}
* @param targetBean 目標物件 使用註解{@link CopyField#originName()}
*/
public static void copyBean(Object originBean, Object targetBean) {
Map<String, Object> originFieldKeyWithValueMap = new HashMap<>(16);
PropertyDescriptor propertyDescriptor = null;
//生成源bean的屬性及其值的字典
generateOriginFieldWithValue(propertyDescriptor, originBean, originFieldKeyWithValueMap, originBean.getClass());
//設定目標bean的屬性值
settingTargetFieldWithValue(propertyDescriptor, targetBean, originFieldKeyWithValueMap, targetBean.getClass());
}
/**
* 生成需要被拷貝的屬性字典 屬性-屬性值<br/>
* 遞迴取父類屬性值
* @param propertyDescriptor 屬性描述器,可以獲取bean中的屬性及方法
* @param originBean 待拷貝的bean
* @param originFieldKeyWithValueMap 存放待拷貝的屬性和屬性值
* @param beanClass 待拷貝的class[可能是超類的class]
*/
private static void generateOriginFieldWithValue(PropertyDescriptor propertyDescriptor, Object originBean, Map<String, Object> originFieldKeyWithValueMap, Class<?> beanClass) {
/**如果不存在超類,那麼跳出迴圈*/
if (beanClass.getSuperclass() == null) {
return;
}
Field[] originFieldList = beanClass.getDeclaredFields();
for (Field field : originFieldList) {
try {
/*獲取屬性上的註解。如果不存在,使用屬性名,如果存在使用註解名*/
CopyField annotation = field.getAnnotation(CopyField.class);
String targetName = "";
if (annotation != null) {
targetName = annotation.targetName();
} else {
targetName = field.getName();
}
//初始化
propertyDescriptor = new PropertyDescriptor(field.getName(), beanClass);
//獲取當前屬性的get方法
Method method = propertyDescriptor.getReadMethod();
//設定值
Object value = method.invoke(originBean);
//設定值
originFieldKeyWithValueMap.put(targetName, value);
} catch (IntrospectionException e) {
logger.warn("【源物件】異常:" + field.getName() + "不存在對應的get方法,無法參與拷貝!");
} catch (IllegalAccessException e) {
logger.warn("【源物件】異常:" + field.getName() + "的get方法執行失敗!");
} catch (InvocationTargetException e) {
logger.warn("【源物件】異常:" + field.getName() + "的get方法執行失敗!");
}
}
//生成超類 屬性-value
generateOriginFieldWithValue(propertyDescriptor, originBean, originFieldKeyWithValueMap, beanClass.getSuperclass());
}
/**
*
* @param propertyDescriptor 屬性描述器,獲取當前傳入屬性的(getter/setter)方法
* @param targetBean 目標容器bean
* @param originFieldKeyWithValueMap 待拷貝的屬性和屬性值
* @param beanClass 待設定的class[可能是超類的class]
*/
private static void settingTargetFieldWithValue(PropertyDescriptor propertyDescriptor, Object targetBean, Map<String, Object> originFieldKeyWithValueMap, Class<?> beanClass) {
/**如果不存在超類,那麼跳出迴圈*/
if (beanClass.getSuperclass() == null) {
return;
}
Field[] targetFieldList = beanClass.getDeclaredFields();
for (Field field : targetFieldList) {
try {
/*獲取屬性上的註解。如果不存在,使用屬性名,如果存在使用註解名*/
CopyField annotation = field.getAnnotation(CopyField.class);
String originName = "";
if (annotation != null) {
originName = annotation.originName();
} else {
originName = field.getName();
}
//初始化當前屬性的描述器
propertyDescriptor = new PropertyDescriptor(field.getName(), beanClass);
//獲取當前屬性的set方法
Method method = propertyDescriptor.getWriteMethod();
method.invoke(targetBean, originFieldKeyWithValueMap.get(originName));
} catch (IntrospectionException e) {
logger.warn("【目標物件】異常:" + field.getName() + "不存在對應的set方法,無法參與拷貝!");
} catch (IllegalAccessException e) {
logger.warn("【目標物件】異常:" + field.getName() + "的set方法執行失敗!");
} catch (InvocationTargetException e) {
logger.warn("【目標物件】異常:" + field.getName() + "的set方法執行失敗!");
}
}
//設定超類屬性
settingTargetFieldWithValue(propertyDescriptor, targetBean, originFieldKeyWithValueMap, beanClass.getSuperclass());
}
}
4.4 測試
/**
* @author : uu
* @version v1.0
* @Description:
* @Date 2019-03-23 09:48
*/
public class MainTest {
public static void main(String[] args) {
OriginUser originUser = new OriginUser();
originUser.setOriginId(1000L);
originUser.setOriginName("張四");
originUser.setPassword("123456");
originUser.setOriginBirthDay(new Date());
originUser.setOriginHealth(true);
//拷貝
TargetUser targetUser = new TargetUser();
BeanUtils.copyBean(originUser, targetUser);
System.out.println(targetUser);
}
}
執行結果:
- TargetUser(targetId=1000, targetName=張四, password=123456, targetBirthDay=Fri Sep 03 13:40:52 CST 2021, targetHealth=true)
5、總結
學習使我充實,分享給我快樂!
- BeanUtils.copyBean()方法支援拷貝超類的屬性,屬性需要有getter和setter方法,否則拋異常(隻影響無get/set方法的屬性)
- PropertyDescriptor屬性描述器,可以很方便的獲取讀取和寫入方法,減少getMethod通過字串拼接獲取方法的成本
- class.getFields()只能獲取公開的屬性,getDeclaredFields可以獲取任意,但只包含本類中,父類需要使用class.getSuperclass()遞歸向上尋找