最近项目有一个跟三方交互的接口,三方用的xml数据,简单记录下玩xml的历程。
首先,对于正常的xml解析,推荐JAXB(Java Architecture for XML Binding) ,JAXB是一个业界的标准,可以根据XML Schema产生Java类的技术。
JDK中JAXB相关的重要Annotation:
@XmlType,将Java类或枚举类型映射到XML模式类型
@XmlAccessorType(XmlAccessType.FIELD) ,控制字段或属性的序列化。FIELD表示JAXB将自动绑定Java类中的每个非静态的(static)、非瞬态的(由@XmlTransient标注)字段到XML。其他值还有XmlAccessType.PROPERTY和XmlAccessType.NONE。
@XmlAccessorOrder,控制JAXB 绑定类中属性和字段的排序。
@XmlJavaTypeAdapter,使用定制的适配器(即扩展抽象类XmlAdapter并覆盖marshal()和unmarshal()方法),以序列化Java类为XML。
@XmlElementWrapper ,对于数组或集合(即包含多个元素的成员变量),生成一个包装该数组或集合的XML元素(称为包装器)。
@XmlRootElement,将Java类或枚举类型映射到XML元素。
@XmlElement,将Java类的一个属性映射到与属性同名的一个XML元素。
@XmlAttribute,将Java类的一个属性映射到与属性同名的一个XML属性。
在以上的注解中,用的最多的是@XMLType,@XmlAccessorType,@XmlRootElement。
举个例子:
import javax.xml.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "RootA")
public class RootA {
@XmlElement(name = "ElementB")
private ElementB b;
@XmlElementWrapper(name="ElementCS")
@XmlElement(name="ElementC")
private List<ElementC> c_s;
@XmlElement(name = "ElementD")
private String d;
}
对于对象B,它的另外一个对象,假设结构如下:
@Data
@XmlAccessorType(XmlAccessType.FIELD)
public class B {
@XmlElement(name = "PropertyB1")
private propertyB1;
@XmlElement(name = "PropertyB2")
private String propertyB2;
}
如此,我们得到一个对象A,xml的数据大概为:
A为根节点,下面有B、C、D 三个对象组成,其中B为另一个对象,C为一个复杂对象List,由数个c构成(c可包含其他复杂对象,如List),D为一个简单的String对象。结构图如下:
对于此种转化工具XmlUtil:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.*;
public class XmlUtil {
/**
* 将对象直接转换成String类型的XML输出
* @param obj 指定对象(包含XML注解)
* @return 返回XML
*/
public static StringconvertToXml(Object obj) {
// 创建输出流
StringWriter sw =new StringWriter();
try {
// 利用jdk中自带的转换类实现
JAXBContext context = JAXBContext.newInstance(obj.getClass());
// 将对象序列化为Xml
Marshaller marshaller = context.createMarshaller();
// 格式化Xml输出的格式
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
// 将对象转换成输出流形式的XML
marshaller.marshal(obj, sw);
}catch (JAXBException e) {
e.printStackTrace();
}
return sw.toString();
}
/**
* 将file类型的xml装换成对象
*/
@SuppressWarnings("unchecked")
public static T convertXmlFileToT(Class clazz,String xmlPath) {
T xmlObject =null;
FileReader fr =null;
try {
JAXBContext context = JAXBContext.newInstance(clazz);
Unmarshaller unmarshal = context.createUnmarshaller();
fr =new FileReader(xmlPath);
xmlObject = (T) unmarshal.unmarshal(fr);
}catch (JAXBException e) {
e.printStackTrace();
}catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
try {
if(fr !=null) {
fr.close();
}
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return xmlObject;
}
/**
* 将String类型的Xml转换成对象
*/
@SuppressWarnings("unchecked")
public static T convertXmlStrToT(Class clazz, String xmlStr) {
T xmlObject =null;
try {
JAXBContext context = JAXBContext.newInstance(clazz);
// 进行将Xml转成对象的核心接口
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader sr =new StringReader(xmlStr);
xmlObject = (T) unmarshaller.unmarshal(sr);
}catch (JAXBException e) {
e.printStackTrace();
}
return xmlObject;
}
/**
* 根据xml路径将其转为对象
*/
public StringconvertPathToT(Class clazz, String path) {
JAXBContext context =null;
// 创建输出流
FileWriter fw =null;
try {
context = JAXBContext.newInstance(clazz);
Marshaller marshaller = context.createMarshaller();
// 格式化xml输出的格式
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
// 将对象转换成输出流形式的xml
try {
fw =new FileWriter(path);
}catch (Exception e) {
e.printStackTrace();
}
marshaller.marshal(clazz, fw);
}catch (JAXBException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return fw.toString();
}
}
对于上述方法,最重要的就是梳理清楚xml的结构,创建对应的类和属性,转换过程很简单,xml作为String输入。
RootA rootA = XmlUtil.convertXmlStrToT(RootA.class,xmlStr);
XmlStr = = XmlUtil.convertToXml(RootA);
在此之后遇到了一个问题,对于
<Elements>
<Element key="key1">value1</ExtendInfo>
</Elements>
对比下一般情况下的结构:
<Elements>
<Element key="key1">
<value1_key>value1</value1_key>
</ExtendInfo>
</Elements>
这种类型的。Elements为Element的List集合。单一对象,key可以用注解@XmlAttribute(name = "key")来解决,但value1无法在不设置key的形式下去set。
所以说下第二种方法,XStream。
XStream优先很多,不在意java类中成员变量是私有还是公有,也不在乎是否有默认构造函数。它调用方式也非常简单:从xml对象转化为java对象,使用fromXML()方法;从java对象序列化为xml,toXML()即可。
使用XStream,需要添加依赖
<groupId>com.thoughtworks.xstream
<artifactId>xstream
<version>1.4.10
</dependency>
如果飙红,试试改下version。
另外,对于在定义别名中的下划线“_”转换为xml后会变成“__”这个符号, 不过下划线问题可以用下面这个解决
1.str.replaceAll(“__“,“_“);
2.XStream xStream = new XStream(new XppDriver(new XmlFriendlyNameCoder("__", "_")));
3.//使用xstream自带的NoNameCoder构造xstream,该方式将导致所有特殊字符都不转义XStream xstream = new XStream(new XppDriver(new NoNameCoder()));
//使用Domdriver及NonameCoder构造xstream,该方式可以指定xml编码方式XStream xstream = new XStream(new DomDriver("UTF-8", new NoNameCoder()));
还是举例说明:
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import com.thoughtworks.xstream.converters.extended.ToAttributedValueConverter;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
@XStreamAlias("RootA")
public class RootA {
@XStreamAlias("ElementB")
private ElementB b;
@XStreamAlias(value ="ElementCS", impl = List.class)
private List<ElementC> c_s;
@XStreamAlias("ElementD")
private String d;
@XStreamAlias(value = "ElementES", impl = List.class)
private List<ElementE> ElementES;
}
对于B、C、D我们保持上个例子,对于E,我们定义如下:
@Data
@XStreamAlias("ElementE")
@XStreamConverter(value = ToAttributedValueConverter.class,strings ="text")
public class ElementE {
@XStreamAsAttribute()
private String key;
private String text;
}
如此,我们就可以得到目标结构,如下:
此方法转换工具:
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import org.apache.commons.lang3.StringUtils;
import org.jdom2.Document;
import org.jdom2.JDOMException;
import com.alibaba.fastjson.JSONObject;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
public class XMLToJson {
/**
* 将Object转换为xml
* @param obj 转换的bean
* @return bean转换为xml
*/
public static StringobjectToXml(Object obj) {
XStream xStream =new XStream();
//xstream使用注解转换
xStream.processAnnotations(obj.getClass());
return xStream.toXML(obj);
}
/**
* 将xml转换为T
* @param <T> 泛型
* @param xml 要转换为T的xml
* @param cls T对应的Class
* @return xml转换为T
*/
public static T xmlToObject(String xml, Class cls){
XStream xstream =new XStream(new DomDriver());
//xstream使用注解转换
xstream.processAnnotations(cls);
return (T) xstream.fromXML(xml);
}
}
补充:对于xml数据前后需要添加的一些声明,比如:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://xxxxxxxxxx" xmlns:web="http://webservice.xxxxxxx/">
<soapenv:Body>
<web:insureService>
<key>value</key>
<datas><![CDATA
可以使用如下方法进行包装:
public StringhandlePostBody(XMLModel model){
String postBody = XMLToJson.objectToXml(model);//XML对应的实体类转化为String类型的xml文本
StringBuffer sb =new StringBuffer("");
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
sb.append("
+"xmlns:soapenv=\"xxxxxx\""
+"xmlns:web=\"http://webservice.xxxxxx\">\n");
sb.append("<soapenv:Body>\n");
sb.append("<web:insureService>\n");
sb.append("<key>value</value>\n");
sb.append("<datas><![CDATA[\n");
sb.append(postBody);
sb.append("]]></datas>\n");
sb.append("</soapenv:Body>\n");
sb.append("</soapenv:Envelope>");
return sb.toString();
}
当我们获取到一长串的xml文本数据,但是有些并不是我们需要的,比如大串的声明、无关紧要的节点数据等。我们需要截取其中的一部分来为我们所用
例如如下数据,我们只需要 ElementAvaible 这个节点内的数据赋值给xmlString,
<RootA>
<ElementB>
<PropertyB1>xxx</PropertyB1>
<PropertyB2>xxx</PropertyB2>
</ElementB>
<ElementAvaible>
<PropertyB1>xxx</PropertyB1>
<PropertyB2>xxx</PropertyB2>
<ElementCS>
<ElementC>
<c1>xx</c1>
<c2>xx</c2>
...
</ElementC>
</ElementCS>
<ElementD>
<d>xxx</d>
</ElementD>
<ElementES>
<ElementE key="key">
text
</ElementE>
</ElementES>
</ElementAvaible>
</RootA>
SAXReader reader =new SAXReader();
Document document =null;
try {
document = (Document) DocumentHelper.parseText(result);
Node EspaNode = document.selectSingleNode(".//*[local-name()='ElementAvaible']");//返回xml中ElementAvaible节点的数据
String note = EspaNode.getText();//获取节点数据
xmlString = note;
}catch (DocumentException e) {
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}
问题解决了,简单总结下。。。
对于JAXB:
1.一种是转换成对象和string类型的xml转换,一种是对象和xml文件进行转换。Java对象和XML文件相互操作有两种方式,
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
/**
* 封装了XML转换成object,object转换成XML的代码
*
* @author Steven
*
*/
public class XMLUtil {
/**
* 将对象直接转换成String类型的 XML输出
*
* @param obj
* @return
*/
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.TRUE);
// 将对象转换成输出流形式的xml
marshaller.marshal(obj, sw);
} catch (JAXBException e) {
e.printStackTrace();
}
return sw.toString();
}
/**
* 将对象根据路径转换成xml文件
*
* @param obj
* @param path
* @return
*/
public static void convertToXml(Object obj, String path) {
try {
// 利用jdk中自带的转换类实现
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = context.createMarshaller();
// 格式化xml输出的格式
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
Boolean.TRUE);
// 将对象转换成输出流形式的xml
// 创建输出流
FileWriter fw = null;
try {
fw = new FileWriter(path);
} catch (IOException e) {
e.printStackTrace();
}
marshaller.marshal(obj, fw);
} catch (JAXBException e) {
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
/**
* 将String类型的xml转换成对象
*/
public static Object convertXmlStrToObject(Class clazz, String xmlStr) {
Object xmlObject = null;
try {
JAXBContext context = JAXBContext.newInstance(clazz);
// 进行将Xml转成对象的核心接口
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader sr = new StringReader(xmlStr);
xmlObject = unmarshaller.unmarshal(sr);
} catch (JAXBException e) {
e.printStackTrace();
}
return xmlObject;
}
@SuppressWarnings("unchecked")
/**
* 将file类型的xml转换成对象
*/
public static Object convertXmlFileToObject(Class clazz, String xmlPath) {
Object xmlObject = null;
try {
JAXBContext context = JAXBContext.newInstance(clazz);
Unmarshaller unmarshaller = context.createUnmarshaller();
FileReader fr = null;
try {
fr = new FileReader(xmlPath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
xmlObject = unmarshaller.unmarshal(fr);
} catch (JAXBException e) {
e.printStackTrace();
}
return xmlObject;
}
}
2.需要转换的model对象一定要添加@XmlRootElement注解,其里面的其他对象则不需要;
3.需要转换的model对象一定要有不带参数的构造方法,包括该对象里面引用的对象
对于XStream:
1.@XStreamAlias(value = "listName", impl = List.class) 只适用复杂对象类型的转换,简单类型的可以使用@XStreamImplicit(itemFieldName ="listName"),但是对于有外层包装的数据,比如
<root>
<desc>this is only a example</desc>
<namelist>
<name>user01</name>
<name>user02</name>
<name>user03</name>
</namelist>
<infolist>
<info>info01</info>
<info>info02</info>
<info>info03</info>
</infolist>
</root>
则需要分别处理对应节点的数据,
XStream xstream = new XStream(new DomDriver());
xstream.processAnnotations(Rootinfo.class);
ClassAliasingMapper mapper = new ClassAliasingMapper(xstream.getMapper());
mapper.addClassAlias("name", String.class);
xstream.registerLocalConverter(
Rootinfo.class, "namelist",
new CollectionConverter(mapper)
);
Rootinfo strXML=(Rootinfo)xstream.fromXML(strXML);
2.XStreamAsAttribute 作用是将类内成员作为父节点属性输出,等同于XStream.useAttributeFor(Object.class, "attribute"),如例子中的ElementE;
XStreamAlias("object") 等同于 xstream.alias("object", Object.class);
XStreamConverter xstreamConvert用于指定class及Field的converter(转换方式),如例子中的ElementE。
XStreamImplicit 注解使用当需要将collection或map类型的成员变量中数据转换成xml相同层次的元素时,可以在该成员变量使用该注解,会将添加注释的节点去掉 @XStreamImplicit(itemFieldName="xxx")
@XStreamOmitField 表明该属性不会被序列化到xml中
3.对于xml转javabean,xstream默认的所有converter均不支持泛型、接口。如果存在超类时,xml中存在子类属性时,转换将出现异常,不包含子类属性时,可转换成功