对接项目和微信公众平台建设中,使用xml格式的报文比较多,一般java项目中会使用dom4j,jdom,SAX等解析方式,这里介绍用XStream解析xml的方法,支持属性和list等结构,具有清晰、简介的特点,具体方案如下:
1、定义与xml节点对应的bo类,假设xml文件如下:
-
则我们可以定义对应的PackageList类:
@XStreamAlias("PackageList")
public class HBPackageList
{
private Attr version;
@XStreamAlias("Package")
private HBPackageInfo hbPackageInfo;
public HBPackageInfo getHbPackageInfo()
{
return hbPackageInfo;
}
public void setHbPackageInfo(HBPackageInfo hbPackageInfo)
{
this.hbPackageInfo = hbPackageInfo;
}
public Attr getVersion()
{
return version;
}
public void setVersion(Attr version)
{
this.version = version;
}
}
这里的Attr为支持属性的bo类,内容如下:
public class Attr
{
private String attrValue;
public Attr(String attrValue)
{
this.attrValue = attrValue;
}
public String getAttrValue()
{
return attrValue;
}
public void setAttrValue(String attrValue)
{
this.attrValue = attrValue;
}
}
@XstreamAlias用注解来重命名属性的名称,HBPackageInfo对应PackageList的一级子节点,如下:
@XStreamAlias("Package")
public class HBPackageInfo
{
@XStreamAlias("Header")
private Header header;
@XStreamAlias("Request")
private HBRequest hbRequest;
public Header getHeader()
{
return header;
}
public void setHeader(Header header)
{
this.header = header;
}
public HBRequest getHbRequest()
{
return hbRequest;
}
public void setHbRequest(HBRequest hbRequest)
{
this.hbRequest = hbRequest;
}
}
我们注意到,某些子节点如CustomList,下面是一个列表对应的子节点,并且每个子节点都有一个不同的key值,这种情况下我们可以用Map结构来描述它,例如ApplyInfo的定义如下:
@XStreamAlias("ApplyInfo")
public class HBApplyInfo
{
@XStreamAlias("Holder")
private CustomList holder;
@XStreamAlias("InsuredInfo")
private HBInsuredInfo hbInsuredInfo;
@XStreamAlias("OtherInfo")
private CustomList otherInfo;
@XStreamAlias("RefundInfo")
private CustomList refundInfo;
public HBInsuredInfo getHbInsuredInfo()
{
return hbInsuredInfo;
}
public void setHbInsuredInfo(HBInsuredInfo hbInsuredInfo)
{
this.hbInsuredInfo = hbInsuredInfo;
}
public CustomList getHolder()
{
return holder;
}
public void setHolder(CustomList holder)
{
this.holder = holder;
}
public CustomList getOtherInfo()
{
return otherInfo;
}
public void setOtherInfo(CustomList otherInfo)
{
this.otherInfo = otherInfo;
}
public CustomList getRefundInfo()
{
return refundInfo;
}
public void setRefundInfo(CustomList refundInfo)
{
this.refundInfo = refundInfo;
}
}
public class CustomList
{
@XStreamAlias("CustomList")
private Map customMap = new HashMap();
public Map getCustomMap()
{
return customMap;
}
public void setCustomMap(Map customMap)
{
this.customMap = customMap;
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface XStreamCDATA
{
}
使用时如下:
public class ResBaseMessage
{
@XStreamCDATA
private String ToUserName;
@XStreamCDATA
private String FromUserName;
private String CreateTime;
@XStreamCDATA
private String MsgType;
public String getToUserName()
{
return ToUserName;
}
public void setToUserName(String toUserName)
{
ToUserName = toUserName;
}
public String getFromUserName()
{
return FromUserName;
}
public void setFromUserName(String fromUserName)
{
FromUserName = fromUserName;
}
public String getCreateTime()
{
return CreateTime;
}
public void setCreateTime(String createTime)
{
CreateTime = createTime;
}
public String getMsgType()
{
return MsgType;
}
public void setMsgType(String msgType)
{
MsgType = msgType;
}
}
2、定义驱动器
请注意上述Attr、Map和XStreamCDATA不是天然就支持的,需要自定义驱动器来支持它,Attr的驱动器定义如下:
public class AttrConverter implements SingleValueConverter
{
public boolean canConvert(Class clazz)
{
return clazz.equals(Attr.class);
}
public String toString(Object obj)
{
return ((Attr)obj).getAttrValue();
}
public Object fromString(String str)
{
return new Attr(str);
}
}
Map的驱动器定义如下:
/**
*
* @类名: MapCustomConverterUtils.java
* @描述:XML解析转化工具,主要是支持将Map转化为特定的格式,如:xxx
* @作者: mxyanx
* @修改日期: 2014年7月1日
*/
public class MapCustomConverterUtils extends AbstractCollectionConverter {
public MapCustomConverterUtils(Mapper mapper) {
super(mapper);
}
/**
* 是否支持的转换Map类型
*/
public boolean canConvert(Class type) {
return type.equals(HashMap.class)
|| type.equals(Hashtable.class)
|| type.getName().equals("java.util.LinkedHashMap")
|| type.getName().equals("sun.font.AttributeMap");
}
/**
* 生成xml文件时的处理方法,将key值set到属性中,将value值set到节点中
*/
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
Map map = (Map) source;
for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
Entry entry = (Entry) iterator.next();
ExtendedHierarchicalStreamWriterHelper.startNode(writer, "Custom", Entry.class);
writer.addAttribute("key", entry.getKey().toString());
writer.setValue(entry.getValue().toString());
writer.endNode();
}
}
/**
* 解析xml文件时的处理方法
*/
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map map = (Map) createCollection(context.getRequiredType());
populateMap(reader, context, map);
return map;
}
/**
*
* 功能描述:由xml文件的节点计算Map的key和value,返回map结构
* @param reader
* @param context
* @param map
*/
protected void populateMap(HierarchicalStreamReader reader, UnmarshallingContext context, Map map) {
while (reader.hasMoreChildren()) {
reader.moveDown();
Object key = reader.getAttribute("key");
Object value = reader.getValue();
map.put(key, value);
reader.moveUp();
}
}
}
CDATA的驱动器如下:
public class CDATAXppDriver extends XppDriver
{
public HierarchicalStreamWriter createWriter(Writer out)
{
return new CDATAPrettyPrintWriter(out);
}
public static XStream createXstream()
{
return new XStream(new XppDriver() {
@Override
public HierarchicalStreamWriter createWriter(Writer out)
{
return new PrettyPrintWriter(out) {
boolean cdata = false;
Class> targetClass = null;
@Override
public void startNode(String name, @SuppressWarnings("rawtypes")
Class clazz)
{
super.startNode(name, clazz);
// 业务处理,对于用XStreamCDATA标记的Field,需要加上CDATA标签
if (!name.equals("xml") && !name.equals("Articles") && !name.equals("item"))
{
cdata = needCDATA(targetClass, name);
}
else
{
targetClass = clazz;
}
}
@Override
protected void writeText(QuickWriter writer, String text)
{
if (cdata)
{
writer.write("");
}
else
{
writer.write(text);
}
}
};
}
});
}
private static boolean needCDATA(Class> targetClass, String fieldAlias)
{
boolean cdata = false;
cdata = existsCDATA(targetClass, fieldAlias);
if (cdata)
{
return cdata;
}
Class> superClass = targetClass.getSuperclass();
while (!superClass.equals(Object.class))
{
cdata = existsCDATA(superClass, fieldAlias);
if (cdata)
{
return cdata;
}
superClass = superClass.getClass().getSuperclass();
}
return false;
}
/**
*
* 功能描述:判断节点是否有CDATA的注解
* @param clazz
* @param fieldAlias
* @return
*/
private static boolean existsCDATA(Class> clazz, String fieldAlias)
{
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields)
{
if (field.getAnnotation(XStreamCDATA.class) != null)
{
XStreamAlias xStreamAlias = field.getAnnotation(XStreamAlias.class);
if (null != xStreamAlias)
{
if (fieldAlias.equals(xStreamAlias.value()))
{
return true;
}
}
else
{
if (fieldAlias.equals(field.getName()))
{
return true;
}
}
}
}
return false;
}
}
public class CDATAPrettyPrintWriter implements ExtendedHierarchicalStreamWriter
{
private final QuickWriter writer;
private final FastStack elementStack = new FastStack(16);
private final char[] lineIndenter;
private boolean tagInProgress;
private int depth;
private boolean readyForNewLine;
private boolean tagIsEmpty;
private static final char[] AMP = "&".toCharArray();
private static final char[] LT = "<".toCharArray();
private static final char[] GT = ">".toCharArray();
private static final char[] SLASH_R = " ".toCharArray();
private static final char[] QUOT = """.toCharArray();
private static final char[] APOS = "'".toCharArray();
private static final char[] CLOSE = "".toCharArray();
public CDATAPrettyPrintWriter(Writer writer, char[] lineIndenter)
{
this.writer = new QuickWriter(writer);
this.lineIndenter = lineIndenter;
}
public CDATAPrettyPrintWriter(Writer writer, String lineIndenter)
{
this(writer, lineIndenter.toCharArray());
}
public CDATAPrettyPrintWriter(PrintWriter writer)
{
this(writer, new char[] { ' ', ' ' });
}
public CDATAPrettyPrintWriter(Writer writer)
{
this(new PrintWriter(writer));
}
public void startNode(String name)
{
tagIsEmpty = false;
finishTag();
writer.write('<');
writer.write(name);
elementStack.push(name);
tagInProgress = true;
depth++;
readyForNewLine = true;
tagIsEmpty = true;
}
public void startNode(String name, Class clazz)
{
startNode(name);
}
public void setValue(String text)
{
readyForNewLine = false;
tagIsEmpty = false;
finishTag();
writeText(writer, text);
}
public void addAttribute(String key, String value)
{
writer.write(' ');
writer.write(key);
writer.write('=');
writer.write('"');
writeAttributue(writer, value);
writer.write('"');
}
protected void writeAttributue(QuickWriter writer, String text)
{
int length = text.length();
for (int i = 0; i < length; i++)
{
char c = text.charAt(i);
switch (c)
{
case '&':
this.writer.write(AMP);
break;
case '<':
this.writer.write(LT);
break;
case '>':
this.writer.write(GT);
break;
case '"':
this.writer.write(QUOT);
break;
case '\'':
this.writer.write(APOS);
break;
case '\r':
this.writer.write(SLASH_R);
break;
default:
this.writer.write(c);
}
}
}
protected void writeText(QuickWriter writer, String text)
{
int length = text.length();
String CDATAPrefix = "':
this.writer.write(GT);
break;
case '"':
this.writer.write(QUOT);
break;
case '\'':
this.writer.write(APOS);
break;
case '\r':
this.writer.write(SLASH_R);
break;
default:
this.writer.write(c);
}
}
}
else
{
for (int i = 0; i < length; i++)
{
char c = text.charAt(i);
this.writer.write(c);
}
}
}
public void endNode()
{
depth--;
if (tagIsEmpty)
{
writer.write('/');
readyForNewLine = false;
finishTag();
elementStack.popSilently();
}
else
{
finishTag();
writer.write(CLOSE);
writer.write((String) elementStack.pop());
writer.write('>');
}
readyForNewLine = true;
if (depth == 0)
{
writer.flush();
}
}
private void finishTag()
{
if (tagInProgress)
{
writer.write('>');
}
tagInProgress = false;
if (readyForNewLine)
{
endOfLine();
}
readyForNewLine = false;
tagIsEmpty = false;
}
protected void endOfLine()
{
writer.write('\n');
for (int i = 0; i < depth; i++)
{
writer.write(lineIndenter);
}
}
public void flush()
{
writer.flush();
}
public void close()
{
writer.close();
}
public HierarchicalStreamWriter underlyingWriter()
{
return this;
}
}
3、解析
在解析属性时,需要用到useAttributeFor方法,在处理Map结构的字段和CDATA数据时,都需要用到驱动器,在处理重命名节点时,需要用到processAnnotations方法,例如:
/**
*
* 功能描述:解析xml文件,并将CustomList的节点以key:value格式输出
* @throws FileNotFoundException
*/
@Test
public void testHbXml2BeanParser() throws FileNotFoundException{
XStream hbXStream = new XStream(new DomDriver());
HBPackageList packageList = new HBPackageList();
FileInputStream hbFis = new FileInputStream("d:/file/hb.xml");
hbXStream.useAttributeFor(HBPackageList.class, "version");
hbXStream.registerConverter(new AttrConverter());
hbXStream.processAnnotations(HBPackageList.class);
hbXStream.registerConverter(new MapCustomConverterUtils(new DefaultMapper(HBPackageList.class.getClassLoader())));
hbXStream.fromXML(hbFis, packageList);
//打印出Holder节点的CustomList包含的Custom节点
Map holderCustomMap = packageList.getHbPackageInfo().getHbRequest().getHbApplyInfo().getHolder().getCustomMap();
System.out.println("************orderid:"+packageList.getHbPackageInfo().getHbRequest().getOrder().getTbOrderId());
for(String key : holderCustomMap.keySet()){
System.out.println(key +":"+holderCustomMap.get(key));
}
System.out.println("*********************");
List hbInsuredList = packageList.getHbPackageInfo().getHbRequest().getHbApplyInfo().getHbInsuredInfo().getInsuredList();
for(HBInsured h : hbInsuredList){
HBBenefitInfo benefitInfo = h.getHbBenefitInfo();
List hbBenefitList = benefitInfo.getHbBenefitList();
for(HBBenefit b : hbBenefitList){
Map customMap = b.getCustomMap();
for(String key : customMap.keySet()){
System.out.println("key:"+key+",value:"+customMap.get(key));
}
}
}
System.out.println("Version:"+packageList.getVersion().getAttrValue());
}
/**
*
* 功能描述:生成xml文件
* @throws FileNotFoundException
*/
@Test
public void testHbResponseBean2Xml() throws FileNotFoundException{
HBResponsePackageList packageList = new HBResponsePackageList();
XStream xStream = new XStream();
packageList.getHbResponsePackageInfo().getResponseHeader().setComId("123");
packageList.getHbResponsePackageInfo().getHbResponseResponse().getHbResponseProposal().setFailReason("成功");
xStream.processAnnotations(HBResponsePackageList.class);
FileOutputStream fs = new FileOutputStream("d:/file/hbResponse.xml");
xStream.toXML(packageList, fs);
}
解析CDATA的数据节点:
@Test
public void testParserTextMessageXml() throws FileNotFoundException
{
XStream txtMsgXStream = new XStream(new CDATAXppDriver());
TextMessage textMessage = new TextMessage();
FileInputStream txtMsgFis = new FileInputStream("d:/file/textMessage.xml");
txtMsgXStream.processAnnotations(TextMessage.class);
txtMsgXStream.fromXML(txtMsgFis, textMessage);
System.out.println("FromUserName:" + textMessage.getFromUserName());
System.out.println("content:" + textMessage.getContent());
}
生成CDATA的数据节点:
@Test
public void testCreateCDATAXml() throws FileNotFoundException
{
ResTextMessage resTextMessage = new ResTextMessage();
resTextMessage.setToUserName("color");
resTextMessage.setFromUserName("yan");
resTextMessage.setCreateTime("" + System.currentTimeMillis());
resTextMessage.setMsgType("text");
resTextMessage.setContent("你好!");
XStream xStream = CDATAXppDriver.createXstream();
xStream.processAnnotations(ResTextMessage.class);
FileOutputStream fs = new FileOutputStream("d:/file/ResTextMessage.xml");
xStream.toXML(resTextMessage, fs);
}
上述代码都经过测试,可以直接使用。