Java物件轉換的思考
阿新 • • 發佈:2020-12-09
物件轉換
- 本文將介紹物件轉換, 在 JavaWeb 開發中我們經常需要對各類物件進行轉換(DB物件,DTO,VO等等).
- 目前解決物件轉換的形式有
- JSON 序列化反序列化 , 存在的問題 欄位名稱需要一樣
- BeanUtils.copyProperties , 存在的問題 欄位名稱需要一樣
- mapstruct, 存在的問題多個介面需要大量記憶
- 有關 mapstruct 的使用文章可以檢視這篇文章
tips:上述觀點僅是筆者所認為的, 並不一定都是問題. 同時歡迎各位讀者指出還有那些工具.
筆者的轉換
-
下面將給各位介紹筆者認為比較好的一種物件轉換方式. 先說說筆者提出的這個方式的缺點.
- 需要手動註冊. (本文並沒有採取包掃描的形式進行因此需要手動註冊)
- 需要手動編寫 convert 邏輯. 筆者並不認為這是一個缺點. 對比 mapstruct 發現有下面的程式碼
- convert 介面的實現類很多.
@Mappings({
@Mapping(source = "id", target = "userId"),
@Mapping(source = "name", target = "userName")
})
UserVO4 toConvertVO(User source);
User fromConvertEntity(UserVO4 userVO4);
筆者所提出的這個設計是將這部分註解形式的程式碼使用 java 編碼
下面開始正式的實現.
實現
- 首先提出問題
- 面對 mapstruct 的使用來說需要記憶大量的轉換類(A的轉換介面是那個), 這部分記憶工作很大. 筆者認為應該從一個方法直接得到. 而不是去程式碼中檢索各類轉換器然後確定是否使用. 針對這個問題筆者定義出下面方法
public class ConvertFacade {
public static <T> T convert(Object source, Class<T> target) {
return null;
}
}
- 解釋: 希望通過一個原始物件+目標物件的型別直接得到結果.
在這個基礎上我們還需要做出 convert 的邏輯. 前文說到筆者將 mapstruct 的註解轉換成了程式碼, 下面來對這部分進行一個分析
public interface Convert<S, T> {
/**
* 轉換
* @param source 原始物件
* @return 轉換結果物件
*/
T convert(S source);
}
- 程式碼部分這裡僅僅是一個介面, 由開發者自定義實現即可.
後續要做的事情就是如何把ConvertFacade
和Convert
串起來.
筆者使用的方式是將 s , t 兩個泛型的類(Class) 獲取將他們組成一個物件ConvertSourceAndTarget
, 將ConvertSourceAndTarget
物件和 當前的 convert 實現類進行繫結 即容器static Map<ConvertSourceAndTarget, Convert> convertMap = new ConcurrentHashMap<>(256);
public class ConvertSourceAndTarget {
private Class<?> sourceTypeClass;
private Class<?> targetTypeClass;
}
-
在資料儲存方式決定後, 需要製作一個可以放入到容器中的方法
register
-
下面是註冊的完整程式碼, 請各位自行閱讀
<details> <summary>register</summary>
public class DefaultConvertRegister implements ConvertRegister {
private static final Logger log = LoggerFactory.getLogger(DefaultConvertRegister.class);
static Map<ConvertSourceAndTarget, Convert> convertMap
= new ConcurrentHashMap<>(256);
public static Convert getConvertMap(ConvertSourceAndTarget param) {
if (log.isInfoEnabled()) {
log.info("getConvertMap,param = {}", param);
}
if (param == null) {
return null;
}
else if (convertMap.containsKey(param)) {
return convertMap.get(param);
}
return null;
}
@Override
public void register(Convert convert) {
if (convert == null) {
log.warn("當前傳入的convert物件為空");
return;
}
Class<? extends Convert> convertClass = convert.getClass();
handler(convert, convertClass);
}
private void handler(Convert convert, Class<? extends Convert> convertClass) {
Type[] genericInterfaces = convertClass.getGenericInterfaces();
for (Type genericInterface : genericInterfaces) {
ParameterizedType pType = (ParameterizedType) genericInterface;
boolean equals = pType.getRawType().equals(Convert.class);
if (equals) {
Type[] actualTypeArguments = pType.getActualTypeArguments();
if (actualTypeArguments.length == 2) {
Type a1 = actualTypeArguments[0];
Type a2 = actualTypeArguments[1];
try {
Class<?> sourceClass = Class.forName(a1.getTypeName());
Class<?> targetClass = Class.forName(a2.getTypeName());
ConvertSourceAndTarget convertSourceAndTarget =
new ConvertSourceAndTarget(sourceClass,
targetClass);
// 如果型別相同 覆蓋
convertMap.put(convertSourceAndTarget, convert);
}
catch (Exception e) {
log.error("a1=[{}]", a1);
log.error("a2=[{}]", a2);
log.error("從泛型中轉換成class異常", e);
}
}
}
}
}
@Override
public void register(Class<? extends Convert> convert) throws IllegalAccessException, InstantiationException {
if (convert == null) {
log.warn("當前傳入的convert物件為空");
return;
}
Convert cv = convert.newInstance();
if (cv != null) {
handler(cv, convert);
}
}
}
</details>
- 萬事俱備 最後只差 ConvertFacade 的呼叫了. 呼叫也就是去找 map 容器中的 convert 物件並且執行方法
convert
public static <T> T convert(Object source, Class<T> target) {
if (log.isInfoEnabled()) {
log.info("convert,source = {}, target = {}", source, target);
}
if (source == null || target == null) {
throw new IllegalArgumentException("引數異常請重新檢查");
}
ConvertSourceAndTarget convertSourceAndTarget = new ConvertSourceAndTarget(source.getClass(), target);
Convert convert = DefaultConvertRegister.getConvertMap(convertSourceAndTarget);
if (convert != null) {
return (T) convert.convert(source);
}
return null;
}
測試
-
首先準備 Source 、Target 、Convert 物件
-
source 物件
public class S {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- target 物件
public class T {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
- convert 介面的實現
public class C1 implements Convert<S, T> {
public C1() {
}
@Override
public T convert(S source) {
T t = new T();
t.setUsername(source.getName());
return t;
}
}
- 測試用例
public class DefaultConvertRegisterTest {
ConvertRegister convertRegister = new DefaultConvertRegister();
@Test
public void register() {
C1 c1 = new C1();
convertRegister.register(c1);
S s = new S();
s.setName("張三");
T convert = ConvertFacade.convert(s, T.class);
Assert.assertEquals(convert.getUsername(), "張三");
}
@Test
public void register2() throws InstantiationException, IllegalAccessException {
convertRegister.register(C1.class);
S s = new S();
s.setName("張三");
T convert = ConvertFacade.convert(s, T.class);
Assert.assertEquals(convert.getUsername(), "張三");
}
}
來源:赤峰SEO