XStream使用中的幾個問題

1、背景

寫接口過程當中,xml和json是最基本的兩種返回類型。html

fastjson能夠很方便的解決json和Pojo之間的轉換,咱們就但願再找一個實現xml和Pojo之間轉換的庫,這樣就能將實例化的對象,根據接口請求返回數據類型,直接轉換成相應格式的返回值。一方面提升開發速度,另外一方面後期方便維護。java

最終決定使用thoughtworks的XStream庫。微信開發中用了一段時間,由於微信涉及的xml格式比較簡單,不少問題沒有出現,如今開發API接口過程當中,一些基本問題就出現了。git

2、問題

一、 Annotation無效github

開始爲了將Pojo對應屬性名改爲想要的,都是使用alias:apache

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

這樣使用太麻煩,最好使用註解json

二、 這裏是列表文本文本內容沒法增長<![CDATA[這是文本]]>微信

從慣例來看,這裏最好使用CDATA包裹微信開發

三、 若是Pojo屬性包含下劃線,生成的xml變成雙下劃線maven

Pojo屬性,不包含下劃線,就不會有這個問題。若是是新項目建議不要使用下劃線,駝峯式仍是首選的。ide

3、解決方法

  1. 官方文檔就有,只是不知道:http://x-stream.github.io/annotations-tutorial.html
XStream stream = new XStream();
xstream.processAnnotations(RendezvousMessage.class);//須要主動調用xstream方法處理類的註解

二、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()

4、參考

參考內容

  1. Annotations Tutorial
  2. 獲取指定包名下的全部類的類名(全名)
  3. XStream註解方式實現生成的XML帶CDATA標籤

資源

  1. XStream源碼包下載
相關文章
相關標籤/搜索