最近開發遇到一個問題,兩個對象進行屬性值拷貝。理論上來講能夠直接藉助org.springframework.beans.BeanUtils.copyProperties(Object source, Object target)來進行拷貝,奈何兩個對象屬性名不一樣,懵逼臉。
待拷貝類java
/** * @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省略*/ }
目標類git
/** * @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()),這種方式簡單粗暴,易寫,不易擴展。若是屬性過多的時候,寫到吐血。github
對象的拷貝,咱們可使用反射進行處理,可是兩個不一樣屬性的對象進行拷貝的問題在於,咱們如何讓兩個不一樣的屬性名進行關聯。順着這個思路,我開始考慮設置一個工具類專門存放兩個對象的屬性對應關係。這個時候問題又出現了,若是有成千上萬的對象,創建關係映射又是浩大的工程。spring
偶然間想到fastJson中利用@JSONField(name="xxx")註解能夠給屬性設置別名,那麼在拷貝不一樣屬性對象時,咱們也可使用這種方案。框架
/** * 該註解應用於類屬性上,主要爲了設置屬性別名,適用於不一樣屬性拷貝 * @author : weenie * @version v1.0 * @Description: 經常使用bean相關方法 */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface CopyField { /** * 在即將被拷貝的屬性上面,設置目標屬性名 * @return */ String targetName() default ""; /** * 在即將拷貝至改屬性上面,設置源屬性名 * @return */ String originName() default ""; }
註解中設置了兩個方法,爲了縮小篇幅,我會同時使用
待拷貝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; }
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); /** * <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()); } }
/** * @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); } }
運行結果:spa
- BeanUtils.copyBean()方法支持拷貝超類的屬性,屬性須要有getter和setter方法,不然拋異常(隻影響無get/set方法的屬性)
- PropertyDescriptor屬性描述器,能夠很方便的獲取讀取和寫入方法,減小getMethod經過字符串拼接獲取方法的成本
- class.getFields()只能獲取公開的屬性,getDeclaredFields能夠獲取任意,但只包含本類中,父類須要使用class.getSuperclass()遞歸向上尋找