data工具,實現了物件複製 DTO -> VO 只需要實現一個類即可
data-utils
data工具,實現了物件複製DTO —> VO
解決的問題
Mapstruct需要安外掛!!!!很多雲桌面等會很不方便
org.springframework.beans.BeanUtils有一個 copyProperties的方法
@Data @AllArgsConstructor @NoArgsConstructor public class AccountDTO { private Long id; private String username; private String password; private String gender; private String email; private String role; private Date registerTime; private Integer isDelete; } @Data public class AccountVO { private String username; private String gender; private String role; //男 1 女 0 private String genderNum; private Integer isDelete; }
比如我們有兩個類 DTO 和 VO 在給前端VO的時候,需要把DTO 轉換成VO 我們需要這樣寫 遇到genderNumDTO類裡沒有的這種情況,我們需要手動set的時候,是下面這種寫法
import com.example.entity.AccountVO; @Data public class DataTest { @Test void contextLoads1() { AccountDTO accountDTO = new AccountDTO(1L, "test", "123456", "男", "112@qq.com", "user", new Date(), 1); AccountVO accountVO = new AccountVO(); BeanUtils.copyProperties(accountDTO, accountVO); accountVO.setGender(Objects.equals(accountDTO.getGender(), "男") ? "1" : "0"); System.out.println(accountVO); } }
有兩個缺點:
1.VO需要手動new一個出來
2.如果VO裡面有很多欄位需要手動set的時候,會把程式碼寫的很長
3.如果遇到集合裡面想要轉換, sonar還會報不讓在迴圈中建立物件的問題
BaseData的作用
BaseData程式碼(專案裡只要有這個就可以)
import com.example.common.Constants; import com.example.common.ErrorCode; import com.example.exception.BusinessException; import org.springframework.util.ReflectionUtils; import java.lang.reflect.*; import java.util.Arrays; import java.util.function.Consumer; public interface BaseData { /** * 預設方法,根據傳入的Class3型別將當前物件轉換為目標物件並執行操作 * * @param clazz 目標類 * @param consumer 可以寫lambda表示式比如 * accountDTO.asTargetObject(AccountVO.class,v->{ * v.setGenderNum(Objects.equals(accountDT0.getGender(),"男")?"1":"0"); * }); * consumer是這段 * v->{ * v.setGenderNum(Objects.equals(accountDT0.getGender(),"男")?"1":"0"); * } */ default <V> V asTargetObject(Class<V> clazz, Consumer<V> consumer) { // 呼叫 asTargetObject 方法將當前物件轉換為目標物件 V v = this.asTargetObject(clazz); // 執行傳入的Consumer操作 consumer.accept(v); return v; } /** * 預設方法 將當前物件轉換為目標物件 * * @param clazz 目標類 * @param <V> 目標類型別 如AccountVO * @return 轉換完的目標類 */ default <V> V asTargetObject(Class<V> clazz) { try { // 獲取目標類的所有欄位 Field[] declaredFields = clazz.getDeclaredFields(); // 獲取目標類的建構函式 Constructor<V> constructor = clazz.getConstructor(); // 根據建構函式例項化目標物件 V v = constructor.newInstance(); // 遍歷目標類的每個欄位,並進行轉換試值 Arrays.stream(declaredFields).forEach(declaredField -> convert(declaredField, v)); return v; } catch (ReflectiveOperationException e) { // //捕獲ReflectiveOperationException異常,丟擲自定義的BusinessException throw new BusinessException(ErrorCode.CAST_OBJECT_ERROR); } } /** * 預設方法,將欄位轉換並賦值給目標物件 * @param field VO剩餘的欄位,自定義 * @param vo 要轉換的VO */ default void convert(Field field, Object vo) { try { // 獲取當前物件中與目標欄位同名的欄位 Field source = this.getClass().getDeclaredField(field.getName()); // 設定欄位可訪問 ReflectionUtils.makeAccessible(field); ReflectionUtils.makeAccessible(source); // 獲取當前物件中獲取欄位值的方法和目標物件中設定欄位值的方法,並進行轉換賦值 Method sourceGetter = this.getClass().getMethod(Constants.GET + capitalize(field.getName())); Method targetSetter = vo.getClass().getMethod(Constants.SET + capitalize(field.getName()), field.getType()); Object value = sourceGetter.invoke(this); targetSetter.invoke(vo, value); } catch (NoSuchFieldException | InvocationTargetException | IllegalAccessException | NoSuchMethodException ignored) { // 這裡ignored 原因是 // 兩個類的欄位數量不一樣的時候,會報 java.lang.NoSuchFieldException // 但是多出來的欄位我們是可以處理的 } } /** * 預設方法,將字串首字母大寫 * @param str 比如欄位名 name * @return 返回 Name */ default String capitalize(String str) { if (str == null || str.isEmpty()) { return str; } return Character.toUpperCase(str.charAt(0)) + str.substring(1); } }
1.實現物件深複製
@Data public class DataTest { @Test void contextLoads1() { AccountDTO accountDTO = new AccountDTO(1L,"test","123456","男","112@qq.com","user",new Date(),1); AccountVO accountVO = accountDTO.asTargetObject(AccountVO.class,v->{ v.setGenderNum(Objects.equals(accountDTO.getGender(), "男") ? "1" : "0"); }); System.out.println(accountVO); } }
1.實現物件Collection深複製(List Set...)
@Data public class DataTest { @Test void contextLoads2() { AccountDTO accountDTO = new AccountDTO(1L,"test","123456","男","112@qq.com","user",new Date(),0); AccountDTO accountDTO2 = new AccountDTO(2L,"test2","123456","女","112@qq.com","admin",new Date(),1); List<AccountDTO> accountDTOList = new ArrayList<>(); accountDTOList.add(accountDTO); accountDTOList.add(accountDTO2); List<AccountVO> list = accountDTOList.stream().map(source -> source.asTargetObject(AccountVO.class, v-> { v.setGenderNum(Objects.equals(source.getGender(), "男") ? "1" : "0"); })).collect(Collectors.toList()); list.forEach(System.out::println); } @Test void contextLoads3() { AccountDTO accountDTO = new AccountDTO(1L,"test","123456","男","112@qq.com","user",new Date(),1); AccountDTO accountDTO2 = new AccountDTO(2L,"test2","123456","女","112@qq.com","admin",new Date(),0); Set<AccountDTO> accountDTOSet = new HashSet<>(); accountDTOSet.add(accountDTO); accountDTOSet.add(accountDTO2); Set<AccountVO> set = accountDTOSet.stream().map(source -> source.asTargetObject(AccountVO.class, v-> { v.setGenderNum(Objects.equals(source.getGender(), "男") ? "1" : "0"); })).collect(Collectors.toSet()); set.forEach(System.out::println); } }
實現步驟
1. dto 實現 BaseData介面 @Data @AllArgsConstructor @NoArgsConstructor public class AccountDTO implements BaseData { private Long id; private String username; private String password; private String gender; private String email; private String role; private Date registerTime; private Integer isDelete; } 2. dto.asViewObject(Target.class); 3. 如果 Target 還有其他欄位 也可以自定義,例如測試用例中的genderNum(只是簡單舉的例子,按照專案實際來) 4. `isDelete` 這種is開頭的也支援 1. 如果有問題,看下lombok版本是否有問題 此專案用的版本是 1.18.28 沒問題 2. 如果沒用lombok 手動加上getIsDelete() 用這個格式就可以了
注意
兩個類 相同的欄位名的欄位型別 必須完全一樣!!!