使用Dom4j、反射自定義實現xml與java對象互轉

一、前言
國慶假期臨近,工作動力不強。所以寫幾篇之前項目中自己用到的一些可能有用的東西分享出來。
今天分享的是Xml與javaBean互轉的自定義實現。
先說幾種我知道的Xml與javaBean互轉的方式:
1、可以利用StringBuilder執行拼接,這樣比較費時且復用性低
2、利用JAXB、jackson等一些公開API調用進行轉換,這樣最方便最簡單
3、利用Dom4j實現
這三種一般來說肯定優先選第二種。
但是出於學習的目的。我在之前的項目中自己利用Dom4j完成xml與javaBean的互轉。
由於使用場景還不多,程式碼健壯性目前有待完善。後續使用在更多場景下會一步一步優化完善,如果有朋友有一些優化建議
歡迎在評論區指出。

**二、公共類 ReflectDTO **
這個介面這裡主要作用是一個標識作用.
如果需要控制類中某些屬性無需轉換。可以加一些註解之類的來控制,由於我沒有這種需求所以沒有實現這功能。
需要轉換的對象來實現這個介面,在處理過程中會將實現該介面的類資訊轉換

import java.io.Serializable;

/**
 * @author hhb
 * @date :2021/8/20 14:23
 */
public interface ReflectDTO extends Serializable {
}

三、Xml轉JavaBean

3.1、方法入口。首先 利用Dom4j 將String格式的xml轉化成Document類型的對象,然後通過遞歸、反射實現轉化

    /**
     * 將Xml格式的字元串轉換為java對象
     * @param xml
     * @param cls
     * @return
     * @throws DocumentException
     * @throws IllegalAccessException
     */
    public static <T> T parseXml(String xml,Class<T> cls) throws DocumentException,IllegalAccessException {
        //xml格式字元串轉Dom4j的Document
        Document document = DocumentHelper.parseText(xml);
        //遞歸處理子元素
        return (T)handleElement(document.getRootElement(), cls,null,null);
    }

3.2、遞歸方法。遞歸處理Document元素,將element裡面的text匹配上對象的屬性並賦值

    /**
     * 遞歸處理元素
     * @param root
     * @param rootClz
     * @param rootObj
     * @param rootField
     * @return
     * @throws IllegalAccessException
     */
    private static Object handleElement(Element root, Class rootClz, Object rootObj,Field rootField) throws IllegalAccessException {
        //創建對象公共方法
        Object fieldObj = creatObj(rootClz);
        List<Element> elements = root.elements();
        //獲取帶轉換類已聲明的屬性
        Field[] fields = rootClz.getDeclaredFields();
        for (Element e:elements){
            //細節處理,後續可進一步完善屬性可能的類型
            handleDetail(fields,e,fieldObj);
        }
        if (null!=rootObj&&null!=rootField){
            rootField.set(rootObj,fieldObj);
        }
        return fieldObj;
    }

3.3、 基礎方法。根據對象的class資訊生成新的對象,還有根據Element的名稱匹配對象中
對應的相應屬性資訊。


    /**
     * 根據類資訊生成對象
     * @param cls
     * @return
     */
    private static Object creatObj(Class cls){
        try {
            return cls.getConstructor().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Documet元素標籤匹配對象屬性
     * @param fields
     * @param name
     * @return
     */
    private static Field matchFields(Field[] fields, String name) {
        for (Field field: fields){
            if (field.getName().equals(name)){
                return field;
            }
        }
        return null;
    }

3.4、 處理核心方法。主要是根據對象屬性的類型採取對應的處理方式目前已實現的有,
實現了ReflectDTO介面的對象、String、List。後面如果還有其他類型可以
自己按照已有示例進行擴展


    /**
     * 處理核心
     * @param fields
     * @param e
     * @param fieldObj
     * @throws IllegalAccessException
     */
    private static void handleDetail(Field[] fields,Element e,Object fieldObj) throws IllegalAccessException {
        //匹配屬性,沒匹配上直接返回
        Field field=matchFields(fields,e.getName());
        if (null==field){
            return;
        }
        field.setAccessible(true);
        Class<?> fieldType = field.getType();
        //如果是List
        if (fieldType.equals(List.class)&&field.getGenericType() instanceof ParameterizedType){
            List list = handleListDetail(e, field);
            field.set(fieldObj,list);
            return;
        }
        //獲得屬性值
        Object childFieldValue = creatObj(fieldType);
        //如果是String
        if (fieldType.equals(String.class)){
            field.set(fieldObj,e.getText());
        }
        //todo 這裡還可以擴展,比如BigDecimal、LocalDateTime、Double等等
        //需要轉換的對象
        if (childFieldValue instanceof ReflectDTO){
            Object o=handleElement(e,fieldType,fieldObj,field);
            field.set(fieldObj,o);
        }
    }

3.5、處理特殊方法。如果待生成屬性是list時的一些處理方法,主要有獲取List裡面的對象的實際類型方法,
處理List屬性時的明細方法。

    /**
     * 列表類型處理明細
     * @param listElement
     * @param field
     * @return
     * @throws IllegalAccessException
     */
    private static List handleListDetail(Element listElement,Field field) throws IllegalAccessException {
        ArrayList objList = new ArrayList<>();
        Class<?> classInList = getClassInList(field.getGenericType());
        for (Element e: listElement.elements()){
            Object objInList = handleElement(e, classInList, null, null);
            objList.add(objInList);
        }
        return objList;
    }

     /**
     * 獲得List裡面的對象類型
     * @param fieldType
     * @return
     */
    private static Class<?> getClassInList(Type fieldType){
        ParameterizedType paramType = (ParameterizedType) fieldType;
        Type[] genericTypes = paramType.getActualTypeArguments();
        if (genericTypes != null && genericTypes.length > 0) {
            if (genericTypes[0] instanceof Class<?>) {
                return  (Class<?>) genericTypes[0];
            }
        }
        return null;
    }

四、 javaBean轉Xml的實現
由於該篇篇幅較長,這個實現將放在下一篇進行說明.