一、背景
写接口过程中,xml和json是最基本的两种返回类型。
fastjson可以很方便的解决json和Pojo之间的转换,我们就希望再找一个实现xml和Pojo之间转换的库,这样就能将实例化的对象,根据接口请求返回数据类型,直接转换成相应格式的返回值。一方面提高开发速度,另一方面后期方便维护。
最终决定使用。微信开发中用了一段时间,因为微信涉及的xml格式比较简单,很多问题没有出现,现在开发API接口过程中,一些基本问题就出现了。
二、问题
1、 Annotation无效
开始为了将Pojo对应属性名改成想要的,都是使用alias:
xstream.alias("item", Item.class);
这样使用太麻烦,最好使用注解
2、 这里是列表文本文本内容无法增加<![CDATA[这是文本]]>
从惯例来看,这里最好使用CDATA包裹
3、 如果Pojo属性包含下划线,生成的xml变成双下划线
Pojo属性,不包含下划线,就不会有这个问题。如果是新项目建议不要使用下划线,驼峰式还是首选的。
三、解决方法
- 官方文档就有,只是不知道:
XStream stream = new XStream();xstream.processAnnotations(RendezvousMessage.class);//需要主动调用xstream方法处理类的注解
2、3. 我们来实现对需要CDATA包裹的属性,添加注解@XStreamCDATA()
- 这里是列表文本定义注解名称
package net.oschina.weixin.tool;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;public class XStreamAnnotation { /** * 为属性增加CDATA包围 * @author buxianglong * @date 2015年10月21日 下午2:43:44 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface XStreamCDATA{ }}
- 自动获取所有@XStreamCDATA注解的属性
package net.oschina.weixin.tool;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.List;import java.util.Set;import org.apache.commons.lang3.StringUtils;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import com.thoughtworks.xstream.XStream;public class XmlTool { private static final Log logger = LogFactory.getLog(XmlTool.class); private static XStream xstream; private static ListCDATA_FIELD = new ArrayList (); private static List > CLASS_ARRAY = new ArrayList >(); private static final String[] packageUrlArray = new String[]{"net.oschina.job.jsonBean"}; static{ List nameOfClasses = new ArrayList (); for(String packageUrl : packageUrlArray){ if(StringUtils.isBlank(packageUrl)){ continue; } Set result = ClassTool.getClassName(packageUrl, false); if(result != null && result.size() > 0){ nameOfClasses.addAll(result); } } if(nameOfClasses != null && nameOfClasses.size() > 0){ for(String nameOfClass : nameOfClasses){ try { Class myClass = Class.forName(nameOfClass); CLASS_ARRAY.add(myClass); //获取自定义注解的属性集合 Field[] fieldArray = myClass.getDeclaredFields(); if(fieldArray != null && fieldArray.length > 0){ for(Field field : fieldArray){ if(field != null && field.isAnnotationPresent(XStreamAnnotation.XStreamCDATA.class)){ CDATA_FIELD.add(field.getName()); } } } } catch (ClassNotFoundException e) { logger.error("net.oschina.weixin.tool.XmlTool.java **XStream** init failed!"); e.printStackTrace(); } } } //实例化XStream对象 xstream = new XStream(new CustomizedDomDriver(CDATA_FIELD)); //处理自带注解 if(CLASS_ARRAY != null && CLASS_ARRAY.size() > 0){ for(Class myClass : CLASS_ARRAY){ if(myClass != null){ xstream.processAnnotations(myClass); } } } } /** * xml转为对象 * @param xml * @return */ public static Object parseXmlToObj(String xml, @SuppressWarnings("rawtypes") Class type){ xstream.alias("xml", type); return xstream.fromXML(xml); } /** * 对象转为xml * @param obj * @return */ public static String parseObjToXml(Object obj){ xstream.alias("xml", obj.getClass()); return xstream.toXML(obj); }}
- 扩展DomDriver,重写createWriter(Writer out)方法
package net.oschina.weixin.tool;import java.io.Writer;import java.util.List;import com.thoughtworks.xstream.core.util.QuickWriter;import com.thoughtworks.xstream.io.HierarchicalStreamWriter;import com.thoughtworks.xstream.io.xml.DomDriver;import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;public class CustomizedDomDriver extends DomDriver{ private ListCDATA_FIELDS; private static XmlFriendlyNameCoder nameCoder = new XmlFriendlyNameCoder("_-", "_"); /** * 构造函数 * @param _CDATA_FIELDS */ public CustomizedDomDriver(List _CDATA_FIELDS){ this.CDATA_FIELDS = _CDATA_FIELDS; } @Override public HierarchicalStreamWriter createWriter(Writer out){ return new PrettyPrintWriter(out, nameCoder){ boolean cdata = false; public void startNode(String name){ super.startNode(name); cdata = CDATA_FIELDS.contains(name); } protected void writeText(QuickWriter writer, String text){ if (cdata){ writer.write(""); }else{ writer.write(text); } } }; }}
- 其中使用了 的Class操作类
package net.oschina.weixin.tool;import java.io.File;import java.io.IOException;import java.net.JarURLConnection;import java.net.URL;import java.net.URLClassLoader;import java.util.Enumeration;import java.util.HashSet;import java.util.Set;import java.util.jar.JarEntry;import java.util.jar.JarFile;/** * http://my.oschina.net/cnlw/blog/299265 * @author 水牛叔叔 * @date 2015年10月20日 下午3:12:49 */public class ClassTool { /** * 获取某包下所有类 * @param packageName 包名 * @param isRecursion 是否遍历子包 * @return 类的完整名称 */ public static SetgetClassName(String packageName, boolean isRecursion) { Set classNames = null; ClassLoader loader = Thread.currentThread().getContextClassLoader(); String packagePath = packageName.replace(".", "/"); URL url = loader.getResource(packagePath); if (url != null) { String protocol = url.getProtocol(); if (protocol.equals("file")) { classNames = getClassNameFromDir(url.getPath(), packageName, isRecursion); } else if (protocol.equals("jar")) { JarFile jarFile = null; try{ jarFile = ((JarURLConnection) url.openConnection()).getJarFile(); } catch(Exception e){ e.printStackTrace(); } if(jarFile != null){ getClassNameFromJar(jarFile.entries(), packageName, isRecursion); } } } else { /*从所有的jar包中查找包名*/ classNames = getClassNameFromJars(((URLClassLoader)loader).getURLs(), packageName, isRecursion); } return classNames; } /** * 从项目文件获取某包下所有类 * @param filePath 文件路径 * @param className 类名集合 * @param isRecursion 是否遍历子包 * @return 类的完整名称 */ private static Set getClassNameFromDir(String filePath, String packageName, boolean isRecursion) { Set className = new HashSet (); File file = new File(filePath); File[] files = file.listFiles(); for (File childFile : files) { if (childFile.isDirectory()) { if (isRecursion) { className.addAll(getClassNameFromDir(childFile.getPath(), packageName+"."+childFile.getName(), isRecursion)); } } else { String fileName = childFile.getName(); if (fileName.endsWith(".class") && !fileName.contains("$")) { className.add(packageName+ "." + fileName.replace(".class", "")); } } } return className; } /** * @param jarEntries * @param packageName * @param isRecursion * @return */ private static Set getClassNameFromJar(Enumeration jarEntries, String packageName, boolean isRecursion){ Set classNames = new HashSet (); while (jarEntries.hasMoreElements()) { JarEntry jarEntry = jarEntries.nextElement(); if(!jarEntry.isDirectory()){ /* * 这里是为了方便,先把"/" 转成 "." 再判断 ".class" 的做法可能会有bug * (FIXME: 先把"/" 转成 "." 再判断 ".class" 的做法可能会有bug) */ String entryName = jarEntry.getName().replace("/", "."); if (entryName.endsWith(".class") && !entryName.contains("$") && entryName.startsWith(packageName)) { entryName = entryName.replace(".class", ""); if(isRecursion){ classNames.add(entryName); } else if(!entryName.replace(packageName+".", "").contains(".")){ classNames.add(entryName); } } } } return classNames; } /** * 从所有jar中搜索该包,并获取该包下所有类 * @param urls URL集合 * @param packageName 包路径 * @param isRecursion 是否遍历子包 * @return 类的完整名称 */ private static Set getClassNameFromJars(URL[] urls, String packageName, boolean isRecursion) { Set classNames = new HashSet (); for (int i = 0; i < urls.length; i++) { String classPath = urls[i].getPath(); //不必搜索classes文件夹 if (classPath.endsWith("classes/")) {continue;} JarFile jarFile = null; try { jarFile = new JarFile(classPath.substring(classPath.indexOf("/"))); } catch (IOException e) { e.printStackTrace(); } if (jarFile != null) { classNames.addAll(getClassNameFromJar(jarFile.entries(), packageName, isRecursion)); } } return classNames; } public static void main(String[] args) { Set classSet = ClassTool.getClassName("net.oschina.job.jsonBean", false); for(String cl: classSet){ System.out.println(cl); } }}
- 关于下划线转换成双下划线的问题,查看xstream的源代码:
/** * Construct a new XmlFriendlyNameCoder. * * @since 1.4 */ public XmlFriendlyNameCoder() { this("_-", "__"); }
所以代码重新定义XmlFriendlyNameCoder()
四、参考
参考内容
资源