使用场景
在Java开发中,有时我们需要自定义Xml,并使用常用的解析器XStream
将Xml字符串
,InputStream
或File
,解析成JavaBean
对象。但是,有些时候,XML的标签属性,我们不需要额外赋值,它们通常只需要保持默认值。例如:
- JavaBean类
@XStreamAlias("controllers")
public class ControllerGenerator {
@XStreamAsAttribute
private String targetProject;
@XStreamAsAttribute
private String targetFolder="src/main/java";
@XStreamAsAttribute
private String targetPackage;
/**
* ......
*/
}
- 对应XML
<controllers targetProject="kuaiban-platform" targetPackage="kuaiban.platform.controller">
/**
* ......
*/
</controllers>
很显然,在XML中我们并没有直接定义属性 targetFolder 的值,而是希望它能够保持 Java类 中定义的初始值
“src/main/java”。
然而,很遗憾,默认情况下,XStream并不支持这种操作。
转换器@XStreamConverter
@XStreamAsAttribute
@XStreamConverter(xxxConverter.class)
private String targetFolder="src/main/java";
首先想到的就是,实现 ConverterMatcher 接口,自定义一个转换器 xxxConverter。
如果,是 BeanToXml,@XStreamConverter将会很好的起到作用。
然而,很不幸,我们需要的是 XmlToBean,其工作原理,实际上就是解析XML中的字节码,而后进行处理(无非是顺序
,判断
,循环
)。
既然,我们的XML内容中根本不包含 targetFolder这个字符串内容,那么程序自然也无法针对性的作做处理,属性targetFolder没有被处理,相应的,它的注解@XStreamConverter(xxxConverter.class),也不可能被处理,所以在这里转换器 @XStreamConverter将失效
。
原理
首先我们要来看一下,XStream的最终构造函数:
XStream xstream = new XStream(
ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver,
ClassLoaderReference classLoaderReference, Mapper mapper, ConverterLookup converterLookup,
ConverterRegistry converterRegistry);
ReflectionProvider 是参数之一,常见实现包含如下:
SunUnsafeReflectionProvider
- XStream 缺省的反射提供器,继承SunLimitedUnsafeReflectionProvider的newInstance(Class)调用 sun.misc.Unsafe 的 native allocateInstance(Class)
本地方法
为目标类分配实例
,属性值默认
为系统初始值 0、false、null。
PureJavaReflectionProvider
- 纯正的java反射提供器,实现接口ReflectionProvider的newInstance(Class)
调用 java.lang.reflect.Constructor 的 newInstance(Class) java反射方法
为目标类创建实例
,属性值为自定义初始值。 用来保证反序列化的时候,使用 javaBean 中的默认值来表示 XML中没有显示指明的字段。 - 但是其中的 writeField(Object,String,Object,Class)方法完全依照 reader 的值向实例中写入, 如果有特殊要求,请复写该方法。
解决方式
FieldDefaultValueProvider —— 自定义反射提供器
- 继承 PureJavaReflectionProvider,复写其中的 writeField(Object,String,Object,Class)方法 为reader值去除首尾的space
- 如果值为空白字符串,取消注入,使用 javaBean中属性的默认值 。
实现代码:
public class FieldDefaultValueProvider extends PureJavaReflectionProvider {
/**
* @param object 目标类的实例
* @param fieldName XML中显示指明的字段
* @param definedIn 父类或者类本身
*/
@Override
public void writeField(Object object, String fieldName, Object value, Class definedIn) {
Field field = fieldDictionary.field(object.getClass(), fieldName, definedIn);//返回存在于xml中的字段
validateFieldAccess(field);//验证字段可以被访问
try {
if (value instanceof String) {
String trim = ((String)value).trim();//字符串首尾去空
if(trim.length()==0)//如果是空字符串,则不做赋值,使用默认初始值
return;
field.set(object,trim);
}else{
field.set(object, value);
}
} catch (IllegalArgumentException e) {
throw new ObjectAccessException("Could not set field " + object.getClass() + "." + field.getName(), e);
} catch (IllegalAccessException e) {
throw new ObjectAccessException("Could not set field " + object.getClass() + "." + field.getName(), e);
}
}
}
使用方式:
public static <T> T toBeanFromFile(InputStream in,Class<T> cls) throws Exception{
XStream xstream=new XStream(new FieldDefaultValueProvider(),new Xpp3Driver());//默认支持DTD
xstream.processAnnotations(cls);
return (T)xstream.fromXML(in);
}