1. 程式人生 > 實用技巧 >Java物件轉換的思考

Java物件轉換的思考

物件轉換

  • 本文將介紹物件轉換, 在 JavaWeb 開發中我們經常需要對各類物件進行轉換(DB物件,DTO,VO等等).
  • 目前解決物件轉換的形式有
    1. JSON 序列化反序列化 , 存在的問題 欄位名稱需要一樣
    2. BeanUtils.copyProperties , 存在的問題 欄位名稱需要一樣
    3. mapstruct, 存在的問題多個介面需要大量記憶
      • 有關 mapstruct 的使用文章可以檢視這篇文章

tips:上述觀點僅是筆者所認為的, 並不一定都是問題. 同時歡迎各位讀者指出還有那些工具.

筆者的轉換

  • 下面將給各位介紹筆者認為比較好的一種物件轉換方式. 先說說筆者提出的這個方式的缺點.

    1. 需要手動註冊. (本文並沒有採取包掃描的形式進行因此需要手動註冊)
    2. 需要手動編寫 convert 邏輯. 筆者並不認為這是一個缺點. 對比 mapstruct 發現有下面的程式碼
    3. convert 介面的實現類很多.
@Mappings({
@Mapping(source = "id", target = "userId"),
@Mapping(source = "name", target = "userName")
})
UserVO4 toConvertVO(User source);

User fromConvertEntity(UserVO4 userVO4);

筆者所提出的這個設計是將這部分註解形式的程式碼使用 java 編碼

下面開始正式的實現.

實現

  • 首先提出問題
    1. 面對 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);
}
  • 程式碼部分這裡僅僅是一個介面, 由開發者自定義實現即可.

後續要做的事情就是如何把ConvertFacadeConvert串起來.

筆者使用的方式是將 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