Java解析XML重型武器JAXB详解

简述 : JDK早期版本就自带JAXB转换功能,一直以来都是JAVA集大成之作,可所谓是JAVA之重器,但是很多时候我们使用过程中并不需要太过复杂可能只是简单的XML序列化和反序列化,今天我就解析XML和生成XML过程中遇到的问题再次记录,希望更多的小伙伴能够找到问题的解决办法.

首先是序列化XML,JAVA提供了以下注解用于描述一个实体对象转化到XML的过程

基本用法

  1. @XmlRootElement

    该注解用于类级别,对应xml的根元素,常与 @XmlType 和 @XmlAccessorType一起使用,主要的作用是映射xml的根节点,name属性标注根节点的名称.

  2. @XmlElement
    将Java对象的属性映射为xml的节点,在使用@XmlElement时,可通过name属性改变java对象属性在xml中显示的名称。

    @XmlElement(name="class")
    private String clz;
    
  3. @XmlElements

    当节点下面包含多个相同的子节点时,该注解可以映射成一个JavaBean的集合属性,其中注解中value的属性对应的是一个@XmlElement的集合

    @XmlElements(value = {@XmlElement(name = "select", type = Select.class)})
    private List<Select> selects;
    
  4. @XmlAccessorType
    用于指定由java对象生成xml文件时对java对象属性的访问方式。常与@XmlRootElement、@XmlType一起使用。它的属性值是XmlAccessType的4个枚举值,分别为:

    • XmlAccessType.FIELD:java对象中的所有成员变量
    • XmlAccessType.PROPERTY:java对象中所有通过getter/setter方式访问的成员变量
    • XmlAccessType.PUBLIC_MEMBER:java对象中所有的public访问权限的成员变量和通过getter/setter方式访问的成员变量
    • XmlAccessType.NONE:java对象的所有属性都不映射为xml的元素

      注意:@XmlAccessorType的默认访问级别是XmlAccessType.PUBLIC_MEMBER,因此,如果java对象中的private成员变量设置了public权限的getter/setter方法,就不要在private变量上使用@XmlElement和@XmlAttribute注解,否则在由java对象生成xml时会报同一个属性在java类里存在两次的错误。同理,如果@XmlAccessorType的访问权限为XmlAccessType.NONE,如果在java的成员变量上使用了@XmlElement或@XmlAttribute注解,这些成员变量依然可以映射到xml文件。

      注意:虽然@XmlAccessorType为XmlAccessType.NONE,但是在java类的私有属性上加了@XmlAttribute和@XmlElement注解后,这些私有成员会映射生成xml的元素

  5. @XmlAccessorOrder
    用于对java对象生成的xml元素进行排序。它有两个属性值:
     AccessorOrder.ALPHABETICAL:对生成的xml元素按字母书序排序
     XmlAccessOrder.UNDEFINED:不排序

  6. @XmlTransient
    用于标示在由java对象映射xml时,忽略此属性。即,在生成的xml文件中不出现此元素。注意XmlAccessType.Filed才会忽略属性,如果是property需要作用于getter方法

  7. @XmlJavaTypeAdapter
    常用在转换比较复杂的对象时,如map类型或者格式化日期等。使用此注解时,需要自己写一个adapter类继承XmlAdapter抽象类,并实现里面的方法。

注意避坑

避坑1:XML转换到Java对象根元素带有命名空间比如

类似这种申明直接反序列化会报错:

javax.xml.bind.UnmarshalException: unexpected element (uri:””, local:”messages”). Expected elements are <{http://www.neusoft.com/hit/rhin}messages>
at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:712)

这里我需要重点介绍 工具类 NamespaceFilter的用法,很多情况下我们在解析XML到Java对象的时候

NamespaceFilter提供了自定义SAXReader解析器的方法跳过命名空间可以正常解析XML

避坑2:

如果你希望转换到XML输出空节点,那么对象的property一定要赋初始值,比如String对象赋””

工具类以及示例代码

以下工具类重点关注:

  1. MarshallerListener空值情况下输出空节点信息
  2. marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);不处理header信息
  3. sax.setNamespaceAware(false);不处理命名空间

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLFilterImpl;
import org.xml.sax.helpers.XMLReaderFactory;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Field;

/**
 * Jaxb2工具类
 *
 * @author zhuc
 * @create 2013-3-29 下午2:40:14
 */
public class JaxbUtil {

    public static String convertToXml(Object obj) {
        // 创建输出流
        StringWriter sw = new StringWriter();
        try {
            // 利用jdk中自带的转换类实现
            JAXBContext context = JAXBContext.newInstance(obj.getClass());
            Marshaller marshaller = context.createMarshaller();
            // 格式化xml输出的格式
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.FALSE);
            // 是否省略xml头信息 默认否
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
            JaxbUtil.MarshallerListener marlistener=new JaxbUtil().new MarshallerListener();
            marshaller.setListener(marlistener);
            // 将对象转换成输出流形式的xml
            marshaller.marshal(obj, sw);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
        return sw.toString();
    }
    /**
     * xml文件配置转换为对象
     * @param xmlStr  xml
     * @param load    java对象.Class
     * @return    java对象
     * @throws JAXBException
     * @throws IOException
     */
    public static <T> T converyToJavaBean(String xmlStr,Class<T> load)  {
        try {
            JAXBContext context = JAXBContext.newInstance(load);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            StringReader sr = new StringReader(xmlStr);
            SAXParserFactory sax = SAXParserFactory.newInstance();
            sax.setNamespaceAware(false);
            XMLReader xmlReader = sax.newSAXParser().getXMLReader();
            Source source = new SAXSource(xmlReader, new InputSource(sr));
            T object = (T) unmarshaller.unmarshal(source);
            return object;
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return null;
    }
    class MarshallerListener extends Marshaller.Listener{
        public static final String BLANK_CHAR = "";
        @Override
        public void beforeMarshal(Object source) {
            super.beforeMarshal(source);
            Field[] fields = source.getClass().getDeclaredFields();
            for (Field f : fields) {
                f.setAccessible(true);
                try {
                    if (f.getType() == String.class && f.get(source) == null) {
                        f.set(source, BLANK_CHAR);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行示例代码

import lombok.Data;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "messages" )
@XmlAccessorType(XmlAccessType.PROPERTY)
@Data
public class ResponseMessage {

    private Switchset switchset = new Switchset();
    private Business business = new Business();
    private Extendset extendset = new Extendset();

    @Data
    public static class Extendset {
        private String token = "";
    }

    @Data
    public static class Business {
        private String standardcode = "";
        private Returnmessage returnmessage = new Returnmessage();
        private Returnset returnset = new Returnset();
        private String datacompress = "";
        private Businessdata businessdata = new Businessdata();

    }

    @Data
    public static class Businessdata {
        private String taskid = "";
    }

    @Data
    public static class Returnset {
        private String rettotal = "";
        private String retpaging = "";
        private String retpageindex = "";
        private String retpageset = "";
    }

    @Data
    public static class Returnmessage {
        private String retcode = "";
        private String rettext = "";
    }

    @Data
    public static class Switchset {
        private Visitor visitor = new Visitor();
        private Serviceinf serviceinf = new Serviceinf();
        private Provider provider = new Provider();
        private String route = "";
        private String process = "";
        private Switchmessage switchmessage = new Switchmessage();
    }

    @Data
    public static class Switchmessage {
        private String messagecode = "";
        private String messagetext = "";

    }

    @Data
    public static class Provider {
        private String targetorgan = "";
        private String targetdomain = "";

    }

    @Data
    public static class Serviceinf {
        private String servicecode = "";
    }

    @Data
    public static class Visitor {
        private String sourceorgan = "";
        private String sourcedomain = "";
    }
}

生成的XML格式如下:

调用工具类执行序列化和反序列化

public static void main(String[] args) throws Exception {

   String xmlStr = JaxbUtil.convertToXml(new ResponseMessage()) ; 
    System.out.println(xmlStr);
    System.out.println(JaxbUtil.convertToBean(xmlStr);
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<messages>
    <business>
        <businessdata>
            <taskid></taskid>
        </businessdata>
        <datacompress></datacompress>
        <returnmessage>
            <retcode></retcode>
            <rettext></rettext>
        </returnmessage>
        <returnset>
            <retpageindex></retpageindex>
            <retpageset></retpageset>
            <retpaging></retpaging>
            <rettotal></rettotal>
        </returnset>
        <standardcode></standardcode>
    </business>
    <extendset>
        <token></token>
    </extendset>
    <switchset>
        <process></process>
        <provider>
            <targetdomain></targetdomain>
            <targetorgan></targetorgan>
        </provider>
        <route></route>
        <serviceinf>
            <servicecode></servicecode>
        </serviceinf>
        <switchmessage>
            <messagecode></messagecode>
            <messagetext></messagetext>
        </switchmessage>
        <visitor>
            <sourcedomain></sourcedomain>
            <sourceorgan></sourceorgan>
        </visitor>
    </switchset>
</messages>

希望这篇文章能够帮助到你,如果你有疑问可以随时联系我!

最后的最后!美图收工

img


  目录