這四種對象屬性拷貝方式幌绍,你都知道嗎赘娄?(第二種)
一把鉴、BeanCopier
BeanCopier是用于在兩個bean之間進行屬性拷貝的故黑。BeanCopier支持兩種方式:
1、一種是不使用Converter的方式庭砍,僅對兩個bean間屬性名和類型完全相同的變量進行拷貝;
2场晶、另一種則引入Converter,可以對某些特定屬性值進行特殊操作怠缸。
1.1 常規(guī)使用
@Test
public void normalCopy() {
// 模擬查詢出數據
UserDO userDO = DataUtil.createData();
log.info("拷貝前:userDO:{}", userDO);
// 第一個參數:源對象诗轻, 第二個參數:目標對象,第三個參數:是否使用自定義轉換器(下面會介紹)揭北,下同
BeanCopier b = BeanCopier.create(UserDO.class, UserDTO.class, false);
UserDTO userDTO = new UserDTO();
b.copy(userDO, userDTO, null);
log.info("拷貝后:userDTO:{}", userDTO);
}
- 拷貝結果
...... 拷貝前:userDO:UserDO(id=1, userName=Van, sex=0, gmtBroth=2019-11-02T18:24:24.077, balance=100)
...... 拷貝后:userDTO:UserDTO(id=1, userName=Van, sex=null)
通過結果發(fā)現(xiàn):UserDO的int類型的sex無法拷貝到UserDTO的Integer的sex扳炬。.
即:BeanCopier 只拷貝名稱和類型都相同的屬性。
即使源類型是原始類型(int, short和char等)搔体,目標類型是其包裝類型(Integer, Short和Character等)恨樟,或反之:都不會被拷貝。
1.2 自定義轉換器
通過1.1可知疚俱,當源和目標類的屬性類型不同時劝术,不能拷貝該屬性,此時我們可以通過實現(xiàn)Converter接口來自定義轉換器
- 目標對象屬性類
@Data
public class UserDomain {
private Integer id;
private String userName;
/**
* 以下兩個字段用戶模擬自定義轉換
*/
private String gmtBroth;
private String balance;
}
- 實現(xiàn)Converter接口來自定義屬性轉換
public class UserDomainConverter implements Converter {
/**
* 時間轉換的格式
*/
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 自定義屬性轉換
* @param value 源對象屬性類
* @param target 目標對象里屬性對應set方法名,eg.setId
* @param context 目標對象屬性類
* @return
*/
@Override
public Object convert(Object value, Class target, Object context) {
if (value instanceof Integer) {
return value;
} else if (value instanceof LocalDateTime) {
LocalDateTime date = (LocalDateTime) value;
return dtf.format(date);
} else if (value instanceof BigDecimal) {
BigDecimal bd = (BigDecimal) value;
return bd.toPlainString();
}
// 更多類型轉換請自定義
return value;
}
}
- 測試方法
/**
* 類型不同,使用Converter
*/
@Test
public void converterTest() {
// 模擬查詢出數據
UserDO userDO = DataUtil.createData();
log.info("拷貝前:userDO:{}", userDO);
BeanCopier copier = BeanCopier.create(UserDO.class, UserDomain.class, true);
UserDomainConverter converter = new UserDomainConverter();
UserDomain userDomain = new UserDomain();
copier.copy(userDO, userDomain, converter);
log.info("拷貝后:userDomain:{}", userDomain);
}
- 拷貝結果
...... 拷貝前:userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T19:51:11.985, balance=100)
...... 拷貝后:userDomain:UserDomain(id=1, userName=Van, gmtBroth=2019-11-02 19:51:11, balance=100)
- 注意:
1呆奕、一旦使用Converter养晋,BeanCopier只使用Converter定義的規(guī)則去拷貝屬性,所以在convert()方法中要考慮所有的屬性梁钾;
2绳泉、毫無疑問,使用Converter會使對象拷貝速度變慢姆泻。
1.3 緩存BeanCopier實例提升性能
BeanCopier拷貝速度快圈纺,性能瓶頸出現(xiàn)在創(chuàng)建BeanCopier實例的過程中秦忿。 所以,把創(chuàng)建過的BeanCopier實例放到緩存中蛾娶,下次可以直接獲取灯谣,提升性能。
- 測試代碼
@Test
public void beanCopierWithCache() {
List<UserDO> userDOList = DataUtil.createDataList(10000);
long start = System.currentTimeMillis();
List<UserDTO> userDTOS = new ArrayList<>();
userDOList.forEach(userDO -> {
UserDTO userDTO = new UserDTO();
copy(userDO, userDTO);
userDTOS.add(userDTO);
});
}
/**
* 緩存 BeanCopier
*/
private static final ConcurrentHashMap<String, BeanCopier> BEAN_COPIERS = new ConcurrentHashMap<>();
public void copy(Object srcObj, Object destObj) {
String key = genKey(srcObj.getClass(), destObj.getClass());
BeanCopier copier = null;
if (!BEAN_COPIERS.containsKey(key)) {
copier = BeanCopier.create(srcObj.getClass(), destObj.getClass(), false);
BEAN_COPIERS.put(key, copier);
} else {
copier = BEAN_COPIERS.get(key);
}
copier.copy(srcObj, destObj, null);
}
private String genKey(Class<?> srcClazz, Class<?> destClazz) {
return srcClazz.getName() + destClazz.getName();
}
1.3 BeanCopier總結
- 當源類和目標類的屬性名稱蛔琅、類型都相同胎许,拷貝沒問題。
- 當源對象和目標對象的屬性名稱相同罗售、類型不同,那么名稱相同而類型不同的屬性不會被拷貝辜窑。注意,原始類型(int寨躁,short穆碎,char)和 他們的包裝類型,在這里都被當成了不同類型职恳,因此不會被拷貝所禀。
- 源類或目標類的setter比getter少,拷貝沒問題放钦,此時setter多余色徘,但是不會報錯。
- 源類和目標類有相同的屬性(兩者的getter都存在)操禀,但是目標類的setter不存在褂策,此時會拋出NullPointerException。
- 加緩存可以提升拷貝速度颓屑。