jaxb实现XML与JavaBean的互相转换遇到的难点(一)

1.首先交代一下背景:

入职后项目组交给我的第一个任务便是做一个酒店直连对接,流程说白了就是发xml报文去携程的系统,携程返回xml里面包含了酒店相关信息,流程听着很简单,但是涉及到跟携程对接、跟公司内部系统对接,作为一个中间层,项目进度很难自己把控,再加上酒店这块业务也挺复杂的,看相关文档就花了两天梳理,所以也很是头疼。

下面说一下技术遇到的难点:事前拿postman往携程系统请求一个酒店的静态信息,结果返回了4M多的xml数据,postman直接崩掉;所以用什么方式解析XML报文要仔细考。Java经常用dom4j解析xml,但是dom4j会将整个XML文档加载到内存中,返回的报文这么大将消耗大量内存;SAX是基于流解析xml的一种技术,但是SAX是读取一段解析一段xml的,速度自然而然非常慢。

上面两种常见的xml解析技术很快被我否定,接着我很快地想到jaxb,jdk6之后它就成为了官网的工具,而且采用的是对象属性与xml的映射;问了一下携程对接人员,没有.xsd文件,可能手动生成那么多对象会比较累,当时觉得麻烦可能仅限于此。

2.实际开工遇到的问题
放上XML就很明显易见了

<Request>
          <Header  AllianceID="12345" SID="12345" TimeStamp="1509679923"  RequestType="OTA" Signature="12345"/>
          <HotelRequest>
            <RequestBody xmlns:ns="http://www.lyyco.cc/OTA/2003/05" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
              <OTA_HotelDescriptiveInfoRQ Version="1.0" xsi:schemaLocation="http://www.lyyco.cc/OTA/2003/05 OTA.xsd" xmlns="http://www.lyyco.cc/OTA/2003/05" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <HotelDescriptiveInfos>
                    <HotelDescriptiveInfo HotelCode="625">
                        <HotelInfo SendData="true"/>
                        <FacilityInfo SendGuestRooms="true"/>
                        <AreaInfo SendAttractions="true"SendRecreations="true"/>
                        <ContactInfo SendData="true"/>
                        <MultimediaObjects SendData="true"/>
                    </HotelDescriptiveInfo>
                </HotelDescriptiveInfos>
              </OTA_HotelDescriptiveInfoRQ>
            </RequestBody>
            </HotelRequest>
        </Request>

请求的xml报文中有好几处命名空间,如果不做对应处理我用Java对象生成的xml是不会有xmlns、xmlns:ns 等命名空间的,这样发送的请求不能进入携程的系统,请求失败。
一般情况下,所有的xml都是有.xsd文件进行格式约束的,况且本来也不建议在非根节点处定义命名空间,于是花了挺长时间研究了如何使生成的xml报文里面有对应的Namespace。先上几个测试类

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.*;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import com.banma.ota.utils.JaxbUtils;
import com.banma.ota.utils.XmlUtil;

@XmlRootElement(name="ClassA")
@XmlAccessorType(XmlAccessType.FIELD)
public class ClassA {
    @XmlElement(name="ClassAID")
    private int classAId;
    @XmlElement(name = "ClassAName")
    private String ClassAName;
    @XmlElement(namespace="http://www.cnblogs.com")
    private ClassB ClassB;
    @XmlElementWrapper
    @XmlElement(name = "ClassC")
    private List<ClassC> classCs;

    public String getClassAName() {
        return ClassAName;
    }
    public void setClassAName(String classAName) {
        ClassAName = classAName;
    }
    public ClassB getClassB() {
        return ClassB;
    }
    public void setClassB(ClassB classB) {
        ClassB = classB;
    }
    public int getClassAId() {
        return classAId;
    }
    public void setClassAId(int classAId) {
        this.classAId = classAId;
    }
    public List<ClassC> getClassCs() {
        return classCs;
    }
    public void setClassCs(List<ClassC> classCs) {
        this.classCs = classCs;
        }
    }
import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
public class ClassB {

    @XmlAttribute
    private int ClassBId;
    @XmlAttribute
    private String ClassBName;

    public int getClassBId() {
        return ClassBId;
    }

    public void setClassBId(int classBId) {
        this.ClassBId = classBId;
    }

    public String getClassBName() {
        return ClassBName;
    }

    public void setClassBName(String classBName) {
        this.ClassBName = classBName;
    }

}
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
@XmlAccessorType(XmlAccessType.FIELD)
public class ClassC {

    @XmlElement(name="Test")
    private String test;

    public String getTest() {
        return test;
    }

    public void setTest(String test) {
        this.test = test;
    }

}

我理想生成的xml是这个样子的:

<?xml version="1.0" encoding="UTF-8"?>
<ClassA xmlns="http://www.cnblogs.com">
  <ClassAID>1</ClassAID>
  <ClassAName>1</ClassAName>
  <ClassB xmlns="http://www.cnblogs.com" classBId="22" classBName="B2"></ClassB>
  <classCs>
    <ClassC>
      <Test>lyy</Test>
    </ClassC>
    <ClassC>
      <Test>tomorrow</Test>
    </ClassC>
  </classCs>
</ClassA>

但是假如不对Jaxb做对应处理,直接映射得到的xml是这样的:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ClassA xmlns:ns2="http://www.cnblogs.com">
    <ClassAID>1</ClassAID>
    <ClassAName>1</ClassAName>
    <ns2:ClassB classBId="22" classBName="B2"/>
    <classCs>
        <ClassC>
            <Test>lyy</Test>
        </ClassC>
        <ClassC>
            <Test>tomorrow</Test>
        </ClassC>
    </classCs>
</ClassA>

存在一个命名空间前缀的问题,以及ClassB节点与预期不相符,直接变成ns2前缀了。
下面放出我的解决办法

import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.*;
import javax.xml.transform.sax.SAXSource;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
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;

/**
 * 带有多个命名空间的XML与javabean转换工具类
 */
public class XmlUtil {

    public static String toXML(Object obj) {
        try {
            JAXBContext context = JAXBContext.newInstance(obj.getClass());

            Marshaller marshaller = context.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");// //编码格式
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);// 是否格式化生成的xml串
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);// 是否省略xm头声明信息

            StringWriter out = new StringWriter();
            OutputFormat format = new OutputFormat();
            format.setIndent(true);
            format.setNewlines(true);
            format.setNewLineAfterDeclaration(false);
            XMLWriter writer = new XMLWriter(out, format);
            /*
             * 通过XMLFiltereImpl匿名内部类实现命名空间和XML节点名称的控制
             */
            XMLFilterImpl nsfFilter = new XMLFilterImpl() {
                private boolean ignoreNamespace = false;
                private String rootNamespace = null;
                private boolean isRootElement = true;

                @Override
                public void startDocument() throws SAXException {
                    super.startDocument();
                }

                @Override
                public void startElement(String uri, String localName, String qName, Attributes atts)
                        throws SAXException {
                    if (this.ignoreNamespace) {
                        uri = "";
                    }
                    if (this.isRootElement) {
                        this.isRootElement = false;
                    } else if (!"".equals(uri) && !localName.contains("xmlns")) {
                        localName = localName + " xmlns=\"" + uri + "\"";
                    }
                    super.startElement(uri, localName, localName, atts);
                }

                @Override
                public void endElement(String uri, String localName, String qName) throws SAXException {
                    if (this.ignoreNamespace){
                        uri = "";}
                    super.endElement(uri, localName, localName);
                }

                @Override
                public void startPrefixMapping(String prefix, String url) throws SAXException {
                    if (this.rootNamespace != null){
                        url = this.rootNamespace;}
                    if (!this.ignoreNamespace){
                        super.startPrefixMapping("", url);}
                }
            };
            nsfFilter.setContentHandler(writer);
            marshaller.marshal(obj, nsfFilter);
            return out.toString();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

核心思路是通过XMLFilterImpl来控制生成的xml节点名称,这样输出的xml与预期的百分百符合;
解决了第一个坑,第二个坑则是解析返回的xml报文时遇到的,同样跟命名空间有关系。
(jaxb实现xml与javabean互相转换 二)待续

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值