JavaBean转XML

前言:

        某些业务场景,需要传输xml文件,本案例基于jackson-xml实现了javaBean向XML的转化,同时自定义了某些注解和DIY序列化方法;支持自定义xml-header信息;支持对list数组的特殊处理,如将list序列化为<list><object><prop></prop></object><object><prop></prop></object></list>  或者 <list><prop></prop></list><list><prop></prop></list>

XmlEncoder:本来是用于openfeign序列化的,现在单独提出来当作工具类使用,主要基于注解实现的序列化功能定制

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import feign.codec.EncodeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
public class XmlEncoder {

    private static final String XML_ELEMENT_SPLIT_SYMBOL = ";";

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    //解析为 xml格式数据
    public static String encode(Object object) throws EncodeException {

        if (Objects.isNull(object)) {
            return null;
        }

        ObjectMapper objectMapper = new XmlMapper();
        String body = null;
        try {
            body = objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException ex) {
            throw new RuntimeException(ex);
        }

        //fix hide list sub table ---- 不带头部信息 ---document.asXML默认会使用utf-8 encoding
        body = hideListSubTable(body, object);

        //append xml header ---- 自定义头部信息
        body = appendHeader(body, object);

        return body;
    }

    private static String hideListSubTable(String body, Object object) {
        //判断是否需要对List列表进行特殊处理
        if (StringUtils.isNotBlank(body) && object.getClass().isAnnotationPresent(XmlListHideSubTableEnabled.class)) {
            XmlListHideSubTableEnabled xmlListHideSubTableEnabled = object.getClass().getAnnotation(XmlListHideSubTableEnabled.class);
            if (!xmlListHideSubTableEnabled.value()) {
                return body;
            }

            //读取Body信息文档
            try {
                //装载Body为Document文档
                Document document = DocumentHelper.parseText(body);

                //查询该文档下需要处理的XML List数组,递归平铺----key: 从根节点开始到当前对象(节点)链路   v: 当前对象(节点)的多个List属性
                Map<String, Set<Field>> nameFieldsMap = findHideListSubTableFields(document.getRootElement().getName(), object);

                //处理List数据
                for (String elePath : nameFieldsMap.keySet()) {
                    Set<Field> fields = nameFieldsMap.get(elePath);
                    for (Field field : fields) {
                        doHideListSubTable(document,elePath, field);
                    }
                }

                //去除头部信息
                body = document.getRootElement().asXML();

            } catch (DocumentException ex) {
                throw new RuntimeException(ex);
            }
        }
        return body;
    }

    /*
     * 源数据结构
     * <root>
     *     <model>
     *         <products>
     *             <product>
     *                 <id>1111</id>
     *             </product>
     *             <product>
     *                 <id>2222</id>
     *             </product>
     *         </products>
     *     </model>
     * <root/>
     *
     * 目标结构
     * <root>
     *     <model>
     *         <products>
     *             <id>1111</id>
     *         </products>
     *         <products>
     *             <id>2222</id>
     *         </products>
     *     </model>
     * <root/>
     *
     * PS:暂不支持List中再次嵌套List的数据处理
     */
    /**
     * @param document  文档
     * @param listTablePath list标签的上级地址,如有数组标签 root;model;products --> 此处则为 root;model ---> /表示为从根节点开始
     * @param field products
     */
    private static void doHideListSubTable(Document document, String listTablePath, Field field) {
        Element parent = findElement(document, listTablePath, true);
        Element listEle = parent.element(getXmlName(field));
        List<List<Element>> listEleContents = findListEleContents(listEle, field);
        if (CollectionUtils.isNotEmpty(listEleContents)) {
            //删除原有的list
            parent.remove(listEle);
            //追加新的list
            for (List<Element> listEleContent : listEleContents) {
                //新增list节点
                Element newListEle = parent.addElement(listEle.getName());
                if (CollectionUtils.isNotEmpty(listEleContent)) {
                    for (Element element : listEleContent) {
                        //为list节点赋值内容
                        newListEle.add(element);
                    }
                }
            }
        }

    }

    private static List<List<Element>> findListEleContents(Element listEle, Field field) {
        if (Objects.isNull(listEle) || Objects.isNull(field) || CollectionUtils.isEmpty(listEle.elements())) {
            return Collections.emptyList();
        }

        String name = field.getName();
        if (field.isAnnotationPresent(JacksonXmlProperty.class)) {
            JacksonXmlProperty annotation = field.getAnnotation(JacksonXmlProperty.class);
            if (StringUtils.isNotBlank(annotation.localName())) {
                name = annotation.localName();
            }
        }

        List<List<Element>> partions = new ArrayList<>();
        for (Element element : listEle.elements()) {
            List<Element> properties = new ArrayList<>();
            if (StringUtils.equals(name, element.getName()) && CollectionUtils.isNotEmpty(element.elements())) {
                for (Element property : element.elements()) {
                    properties.add((Element) property.clone());
                }
            }
            if (CollectionUtils.isNotEmpty(properties)) {
                partions.add(properties);
            }

        }
        return partions;
    }

    private static Element findElement(Document document, String listTablePath, boolean pathIncludeRoot) {

        Element element = document.getRootElement();

        if (StringUtils.isBlank(listTablePath)) {
            return element;
        }

        List<String> eleNames = Arrays.stream(listTablePath.split(XML_ELEMENT_SPLIT_SYMBOL)).collect(Collectors.toList());
        if (pathIncludeRoot) {
            eleNames = eleNames.stream().skip(1).collect(Collectors.toList());
        }

        for (String eleName : eleNames) {
            element = element.element(eleName);
            if (Objects.isNull(element)) {
                throw new RuntimeException("can not find xml element from path: " + listTablePath + ", error ele name: " + eleName);
            }
        }

        return element;
    }

    private static Map<String, Set<Field>> findHideListSubTableFields(String name, Object object) {
        //root name
        if (StringUtils.isBlank(name)) {
            name = getXmlName(object);
        }
        Map<String, Set<Field>> nameFieldsMap = new HashMap<>();
        Set<Field> fieldSet = new HashSet<>();
        Field[] fields = object.getClass().getDeclaredFields();
        BeanWrapper beanWrapper = new BeanWrapperImpl(object);
        for (Field field : fields) {
            if (!isJavaClass(field.getType())) {
                String eleName = getXmlName(field);
                nameFieldsMap.putAll(findHideListSubTableFields(name + XML_ELEMENT_SPLIT_SYMBOL + eleName,
                        beanWrapper.getPropertyValue(field.getName())));
            }
            if (field.isAnnotationPresent(XmlListHideSubTable.class) && field.getAnnotation(XmlListHideSubTable.class).value()) {
                fieldSet.add(field);
            }
        }
        if (CollectionUtils.isNotEmpty(fieldSet)) {
            nameFieldsMap.put(name, fieldSet);
        }
        return nameFieldsMap;
    }

    private static String getXmlName(Object object) {
        if (object.getClass().isAnnotationPresent(JacksonXmlRootElement.class)) {
            return object.getClass().getAnnotation(JacksonXmlRootElement.class).localName();
        }

        String name = object.getClass().toString();
        if (object instanceof Field) {
            Field field = (Field) object;
            if (field.isAnnotationPresent(JacksonXmlElementWrapper.class)) {
                JacksonXmlElementWrapper anno = field.getAnnotation(JacksonXmlElementWrapper.class);
                if (StringUtils.isNotBlank(anno.localName())) {
                    name = anno.localName();
                }
            } else if (field.isAnnotationPresent(JacksonXmlProperty.class)) {
                JacksonXmlProperty anno = field.getAnnotation(JacksonXmlProperty.class);
                if (StringUtils.isNotBlank(anno.localName())) {
                    name = anno.localName();
                }
            }
        }
        return name;
    }

    //判断是否为内置对象,false-->则为用户自定义对象
    private static boolean isJavaClass(Class<?> clz) {
        return clz != null && clz.getClassLoader() == null;
    }

    //判断是否为数组--集合--map类型
    private static boolean isMultiClass(Class<?> clazz) {
        return null != clazz && (clazz.isArray()
                || Collection.class.isAssignableFrom(clazz)
                || Map.class.isAssignableFrom(clazz));
    }

    private static String appendHeader(String body, Object object) {
        if (object.getClass().isAnnotationPresent(XmlHeader.class)) {
            String xmlHeader = object.getClass().getAnnotation(XmlHeader.class).value();
            body = xmlHeader + body;
        }

        return body;
    }


    public static String parseJsonPayload(EmailRequest emailRequest) {
        String xmlStr = encode(emailRequest);
        if (StringUtils.isBlank(xmlStr)) {
            return null;
        }

        // 匹配规则
        //提取xml中的标签
        String xml = extractXml(xmlStr, emailRequest.getEmailBodyNodeNames());
        //转义xml 为 str给到notification
        return StringEscapeUtils.escapeXml(xml);
    }

    private static String extractXml(String xmlStr, List<String> xmlNames) {
        if (CollectionUtils.isEmpty(xmlNames)) {
            return xmlStr;
        }
        StringBuilder sb = new StringBuilder();
        for (Object xmlName : xmlNames) {
            String startNode = "<" + xmlName;
            String endNode = "</" + xmlName + ">";
            String reg = startNode + "(.*?)" + endNode;
            Pattern pattern = Pattern.compile(reg);
            Matcher matcher = pattern.matcher(xmlStr);
            while (matcher.find()) {
                sb.append(matcher.group());
            }
        }
        return sb.toString();
    }

}

PS:以上代码使用encode方法就好,其他的方法是基于encode之后的数据做了一些xml标签的提取工作

相关注解:

//JacksonXmlElementWrapper: jackson的标记list的注解,encode的时候会获取该注解信息
//JacksonXmlProperty:jackson的标记对象标签或者值标签的注解
//JacksonXmlRootElement: jackson的标记根标签的注解

//XmlHeader: 自定义注解,标记在最外层的javaBean上,和xml最外层跟标签一个层级, encode的时候获取该注解信息---> xml头信息
//XmlListHideSubTable: 自定义注解, 标记在List<Model>的javabean属性上,用于判定是否要对list进行特殊处理
//XmlListHideSubTableEnabled: 自定义注解, 标记在最外层的javaBean上,和xml最外层跟标签一个层级

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlHeader {

    String value() default "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";

}

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlListHideSubTable {

    boolean value() default true;

}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlListHideSubTableEnabled {

    boolean value() default true;

}

JavaBean写法

@XmlListHideSubTableEnabled
@XmlHeader
@JsonIgnoreProperties(ignoreUnknown = true)
@JacksonXmlRootElement(localName = "Root")
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class EmailContentLeaTempRequest extends EmailRequest {

    @Builder.Default
    @JacksonXmlProperty(localName = "xmlns", isAttribute = true)
    private String ns = "ns1";

    @Builder.Default
    @JacksonXmlProperty(localName = "xmlns:xsi", isAttribute = true)
    private String otherNs = "ns2";


    @JacksonXmlProperty(localName = "Model")
    private Model model;


    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Model {

        @JacksonXmlProperty(localName = "Detail")
        private Detail detail;

        @JsonIgnoreProperties(ignoreUnknown = true)
        @Data
        @Builder
        @AllArgsConstructor
        @NoArgsConstructor
        public static class Detail {

            @Builder.Default
            @JacksonXmlProperty(localName = "xmlns", isAttribute = true)
            private String namespace = "http://xxx.detail/1.0";

            @XmlListHideSubTable
            @JacksonXmlProperty(localName = "Product")
            @JacksonXmlElementWrapper(localName = "Products")
            private List<Product> products;

            @Data
            @Builder
            @AllArgsConstructor
            @NoArgsConstructor
            public static class Product {

                @JacksonXmlProperty(localName = "Name")
                private String productName;

            }
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值