对接项目和微信公众平台建设中,使用xml格式的报文比较多,一般java项目中会使用dom4j,jdom,SAX等解析方式,这里介绍用XStream解析xml的方法,支持属性和list等结构,具有清晰、简介的特点,具体方案如下:
1、定义与xml节点对应的bo类,假设xml文件如下:
<PackageList version="1.0"> <Package> <Header> <RequestType></RequestType> <UUID></UUID> <ComId></ComId> <From></From> <SendTime></SendTime> <Asyn></Asyn> <ReturnUrl></ReturnUrl> <ProductCode></ProductCode> </Header> <Request> <Order> <TBOrderId></TBOrderId> <TotalPremium></TotalPremium> <PostFee></PostFee> <ApplyNum></ApplyNum> <Item> <ItemId></ItemId> <SkuRiskCode></SkuRiskCode> <ProductCode></ProductCode> <ProductName></ProductName> <Premium></Premium> <ActualPremium></ActualPremium> <DiscountRate></DiscountRate> </Item> </Order> <ApplyInfo> <Holder> <CustomList> <Custom key="HolderName"></Custom> <Custom key="save-holder"></Custom> <Custom key="HolderCardType"></Custom> <Custom key="HolderCardNo"></Custom> <Custom key="HolderBirthday"></Custom> <Custom key="HolderSex"></Custom> <Custom key="HolderEmail"></Custom> </CustomList> </Holder> <InsuredInfo> <IsHolder></IsHolder> <InsuredList> <Insured> <CustomList> <Custom key="InsuredRelation"></Custom> <Custom key="InsuredName"></Custom> <Custom key="InsuredCardType"></Custom> <Custom key="InsuredCardNo"></Custom> <Custom key="InsuredBirthday"></Custom> <Custom key="InsuredSex"></Custom> <Custom key="InsuredEmail"></Custom> </CustomList> <BenefitInfo> <IsLegal></IsLegal> <BenefitList> <Benefit> <CustomList> <Custom key="BenefitName"></Custom> <Custom key="BenefitScale"></Custom> <Custom key="BenefitRelation"></Custom> </CustomList> </Benefit> </BenefitList> </BenefitInfo> </Insured> </InsuredList> </InsuredInfo> <OtherInfo> <CustomList/> </OtherInfo> <RefundInfo> <CustomList/> </RefundInfo> </ApplyInfo> </Request> </Package> </PackageList>
则我们可以定义对应的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; } }
如此这样循环嵌套定义,即可定义出一套完整的xml结构对应的类结构......
我们注意到,某些子节点如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; } }
其中CustomList的定义如下:
public class CustomList { @XStreamAlias("CustomList") private Map<String,String> customMap = new HashMap<String,String>(); public Map<String,String> getCustomMap() { return customMap; } public void setCustomMap(Map<String,String> customMap) { this.customMap = customMap; } }
还有一种情况,某些节点使用CDATA的结构包装节点间的数据时,需要对CDATA节点进行解析,这时我们自定义注解类如下:
@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转化为特定的格式,如:<node key="xxx">xxx</node> * @作者: 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("<![CDATA[" + text + "]]>"); } 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 = "<![CDATA["; if (!text.startsWith(CDATAPrefix)) { 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); } } } 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<String,String> 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<HBInsured> hbInsuredList = packageList.getHbPackageInfo().getHbRequest().getHbApplyInfo().getHbInsuredInfo().getInsuredList(); for(HBInsured h : hbInsuredList){ HBBenefitInfo benefitInfo = h.getHbBenefitInfo(); List<HBBenefit> hbBenefitList = benefitInfo.getHbBenefitList(); for(HBBenefit b : hbBenefitList){ Map<String,String> 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文件:
/** * * 功能描述:生成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); }
上述代码都经过测试,可以直接使用。