摘要:前言最近開發遇到一個問題,兩個對象進行屬性值拷貝。理論上來說可以直接借助來進行拷貝,奈何兩個對象屬性名不同,懵逼臉。
1、前言
最近開發遇到一個問題,兩個對象進行屬性值拷貝。理論上來說可以直接借助org.springframework.beans.BeanUtils.copyProperties(Object source, Object target)來進行拷貝,奈何兩個對象屬性名不同,懵逼臉。2、問題引入
待拷貝類
/** * @author : weenie * @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 : weenie * @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 : weenie * @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 : weenie * @version v1.0 * @Description: 常用bean相關方法 */ public class BeanUtils { private static Logger logger = LoggerFactory.getLogger(BeanUtils.class); /** *4.4 測試拷貝一個對象的屬性至另一個對象
** 支持兩個對象之間不同屬性名稱進行拷貝,使用注解{@link CopyField} *
* @param originBean 源對象 使用注解{@link CopyField#targetName()} * @param targetBean 目標對象 使用注解{@link CopyField#originName()} */ public static void copyBean(Object originBean, Object targetBean) { MaporiginFieldKeyWithValueMap = new HashMap<>(16); PropertyDescriptor propertyDescriptor = null; //生成源bean的屬性及其值的字典 generateOriginFieldWithValue(propertyDescriptor, originBean, originFieldKeyWithValueMap, originBean.getClass()); //設置目標bean的屬性值 settingTargetFieldWithValue(propertyDescriptor, targetBean, originFieldKeyWithValueMap, targetBean.getClass()); } /** * 生成需要被拷貝的屬性字典 屬性-屬性值
* 遞歸取父類屬性值 * @param propertyDescriptor 屬性描述器,可以獲取bean中的屬性及方法 * @param originBean 待拷貝的bean * @param originFieldKeyWithValueMap 存放待拷貝的屬性和屬性值 * @param beanClass 待拷貝的class[可能是超類的class] */ private static void generateOriginFieldWithValue(PropertyDescriptor propertyDescriptor, Object originBean, MaporiginFieldKeyWithValueMap, 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 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()); } }
/** * @author : weenie * @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); } }
運行結果:
5、總結BeanUtils.copyBean()方法支持拷貝超類的屬性,屬性需要有getter和setter方法,否則拋異常(只影響無get/set方法的屬性)
PropertyDescriptor屬性描述器,可以很方便的獲取讀取和寫入方法,減少getMethod通過字符串拼接獲取方法的成本
class.getFields()只能獲取公開的屬性,getDeclaredFields可以獲取任意,但只包含本類中,父類需要使用class.getSuperclass()遞歸向上尋找
Diboot - 簡單高效的輕代碼開發框架
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73873.html
摘要:面試通關要點匯總集部分解答說明如果你有幸能看到的話,本文整體框架來自阿里梁桂釗的博文,總結的非常不錯。這樣做的目的是對內部數據進行了不同級別的保護,防止錯誤的使用了對象的私有部分。被繼承的類稱為基類和父類或超類。 showImg(https://segmentfault.com/img/remote/1460000013442471?w=1280&h=819); Java面試通關要點匯...
摘要:使用反射可以在運行時檢視類。類名類修飾符等包信息超類所實現的接口構造函數方法屬性注解類中附加了很多信息,你可以在獲得一個完整列表。全限定名包含所有的包名。構造函數你可以訪問類的構造函數,代碼如下構造函數的詳細教程在章節。 使用反射可以在運行時檢視Java類。檢視類通常是使用反射時所做的第一件事情。從類中可以獲得下面的信息。 類名 類修飾符(private、public、synchro...
摘要:第章元編程與注解反射反射是在運行時獲取類的函數方法屬性父類接口注解元數據泛型信息等類的內部信息的機制。本章介紹中的注解與反射編程的相關內容。元編程本質上是一種對源代碼本身進行高層次抽象的編碼技術。反射是促進元編程的一種很有價值的語言特性。 第12章 元編程與注解、反射 反射(Reflection)是在運行時獲取類的函數(方法)、屬性、父類、接口、注解元數據、泛型信息等類的內部信息的機...
摘要:在這一步里,將配置文件的信息裝入到容器的定義注冊表中,但此時還未初始化。注冊后處理器根據反射機制從中找出所有類型的,并將它們注冊到容器后處理器的注冊表中。是屬性編輯器的注冊表,主要作用就是注冊和保存屬性編輯器。 點擊進入我的博客 1 Spring容器整體流程 1.1 ApplicationContext內部原理 AbstractApplicationContext是Applicati...
閱讀 2060·2021-11-23 09:51
閱讀 3353·2021-09-28 09:36
閱讀 1120·2021-09-08 09:35
閱讀 1758·2021-07-23 10:23
閱讀 3258·2019-08-30 15:54
閱讀 2998·2019-08-29 17:05
閱讀 438·2019-08-29 13:23
閱讀 1294·2019-08-28 17:51