jaxb实现XML与JavaBean的互相转换遇到的难点(一)
2017-11-11 16:02
579 查看
1.首先交代一下背景:
入职后项目组交给我的第一个任务便是做一个酒店直连对接,流程说白了就是发xml报文去携程的系统,携程返回xml里面包含了酒店相关信息,流程听着很简单,但是涉及到跟携程对接、跟公司内部系统对接,作为一个中间层,项目进度很难自己把控,再加上酒店这块业务也挺复杂的,看相关文档就花了两天梳理,所以也很是头疼。
下面说一下技术遇到的难点:事前拿postman往携程系统请求一个酒店的静态信息,结果返回了4M多的xml数据,postman直接崩掉;所以用什么方式解析XML报文要仔细考。Java经常用dom4j解析xml,但是dom4j会将整个XML文档加载到内存中,返回的报文这么大将消耗大量内存;SAX是基于流解析xml的一种技术,但是SAX是读取一段解析一段xml的,速度自然而然非常慢。
上面两种常见的xml解析技术很快被我否定,接着我很快地想到jaxb,jdk6之后它就成为了官网的工具,而且采用的是对象属性与xml的映射;问了一下携程对接人员,没有.xsd文件,可能手动生成那么多对象会比较累,当时觉得麻烦可能仅限于此。
2.实际开工遇到的问题
放上XML就很明显易见了
请求的xml报文中有好几处命名空间,如果不做对应处理我用Java对象生成的xml是不会有xmlns、xmlns:ns 等命名空间的,这样发送的请求不能进入携程的系统,请求失败。
一般情况下,所有的xml都是有.xsd文件进行格式约束的,况且本来也不建议在非根节点处定义命名空间,于是花了挺长时间研究了如何使生成的xml报文里面有对应的Namespace。先上几个测试类
我理想生成的xml是这个样子的:
但是假如不对Jaxb做对应处理,直接映射得到的xml是这样的:
存在一个命名空间前缀的问题,以及ClassB节点与预期不相符,直接变成ns2前缀了。
下面放出我的解决办法
核心思路是通过XMLFilterImpl来控制生成的xml节点名称,这样输出的xml与预期的百分百符合;
解决了第一个坑,第二个坑则是解析返回的xml报文时遇到的,同样跟命名空间有关系。
(jaxb实现xml与javabean互相转换 二)待续
入职后项目组交给我的第一个任务便是做一个酒店直连对接,流程说白了就是发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"> 4000 ; <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.ignoreNames a6b2 pace) { 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互相转换 二)待续
相关文章推荐
- jaxb实现XML与JavaBean的互相转换遇到的难点(二)
- JAXB实现JavaBean与XML互相转换
- JAXB实现JavaBean和xml互相转换
- 使用JAXB实现JAVA对象和XML字符串的互相转换实例
- jaxb实现xml与javaBean相互转换
- JAXB 实现java对象与xml之间互相转换
- JAXB 实现java对象与xml之间互相转换
- 依靠JAXBContext轻松实现Java和xml的互相转换
- JAXB 实现java对象与xml之间互相转换
- 使用JAXB实现JAVA对象和XML字符串的互相转换实例
- java对象和xml的互相转换,JAXB可以轻松实现
- 通过jaxb实现javabean和xml的转换
- 利用JAXB实现java实体类和xml互相转换
- 使用JAXB实现JAVA对象和XML字符串的互相转换
- JAXB实现xml与javabean的转换
- JAXB 实现 XML & JAVABEAN 的转换
- JAXB 实现java对象与xml之间互相转换
- 利用JAXB实现java实体类和xml互相转换
- JAXB 实现java对象与xml之间互相转换
- 使用JAXB实现JAVA对象和XML字符串的互相转换