使用XStream解析Xml定义属性默认值

使用场景

在Java开发中,有时我们需要自定义Xml,并使用常用的解析器XStream将Xml字符串InputStreamFile,解析成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 缺省反射提供器,继承SunLimitedUnsafeReflectionProvidernewInstance(Class)调用 sun.misc.Unsafenative allocateInstance(Class) 本地方法为目标类分配实例属性值 默认为系统初始值 0falsenull

PureJavaReflectionProvider

  • 纯正的java反射提供器,实现接口ReflectionProvidernewInstance(Class)
    调用 java.lang.reflect.ConstructornewInstance(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);        
} 

猜你喜欢

转载自blog.csdn.net/qq_32331073/article/details/79942472
今日推荐