XStream使用中的几个问题

一、背景

写接口过程中,xml和json是最基本的两种返回类型。

fastjson可以很方便的解决json和Pojo之间的转换,我们就希望再找一个实现xml和Pojo之间转换的库,这样就能将实例化的对象,根据接口请求返回数据类型,直接转换成相应格式的返回值。一方面提高开发速度,另一方面后期方便维护。

最终决定使用thoughtworks的XStream库。微信开发中用了一段时间,因为微信涉及的xml格式比较简单,很多问题没有出现,现在开发API接口过程中,一些基本问题就出现了。

二、问题

1、 Annotation无效

开始为了将Pojo对应属性名改成想要的,都是使用alias:

xstream.alias("item", Item.class);

这样使用太麻烦,最好使用注解

2、 这里是列表文本文本内容无法增加<![CDATA[这是文本]]>

从惯例来看,这里最好使用CDATA包裹

3、 如果Pojo属性包含下划线,生成的xml变成双下划线

Pojo属性,不包含下划线,就不会有这个问题。如果是新项目建议不要使用下划线,驼峰式还是首选的。

三、解决方法

  1. 官方文档就有,只是不知道:http://x-stream.github.io/annotations-tutorial.html
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 List<String> CDATA_FIELD = new ArrayList<String>();
	private static List<Class<?>> CLASS_ARRAY = new ArrayList<Class<?>>();
	private static final String[] packageUrlArray = new String[]{"net.oschina.job.jsonBean"};

	static{
		List<String> nameOfClasses = new ArrayList<String>();
		for(String packageUrl : packageUrlArray){
			if(StringUtils.isBlank(packageUrl)){
				continue;
			}
			Set<String> 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 List<String> CDATA_FIELDS;
	 private static XmlFriendlyNameCoder nameCoder = new XmlFriendlyNameCoder("_-", "_");
	 /**
	  * 构造函数
	  * @param _CDATA_FIELDS
	  */
	 public CustomizedDomDriver(List<String> _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("<![CDATA[");
					 writer.write(text);
					 writer.write("]]>");
				 }else{
					 writer.write(text);
				 }
			 }
		 };
	 }
}
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 Set<String> getClassName(String packageName, boolean isRecursion) {
        Set<String> 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<String> getClassNameFromDir(String filePath, String packageName, boolean isRecursion) {
        Set<String> className = new HashSet<String>();
        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<String> getClassNameFromJar(Enumeration<JarEntry> jarEntries, String packageName, boolean isRecursion){
        Set<String> classNames = new HashSet<String>();

        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<String> getClassNameFromJars(URL[] urls, String packageName, boolean isRecursion) {
        Set<String> classNames = new HashSet<String>();

        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<String> 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()

四、参考

参考内容

  1. Annotations Tutorial
  2. 获取指定包名下的所有类的类名(全名)
  3. XStream注解方式实现生成的XML带CDATA标签

资源

  1. XStream源码包下载

转载于:https://my.oschina.net/ijustdoit/blog/520719

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值