一、背景
写接口过程中,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属性,不包含下划线,就不会有这个问题。如果是新项目建议不要使用下划线,驼峰式还是首选的。
三、解决方法
- 官方文档就有,只是不知道: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);
}
}
};
}
}
- 其中使用了@水牛叔叔 的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 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()
四、参考
参考内容
资源