解决XStream解析第三方的xml的问题

一、XStream解析第三方的xml的问题

问题描述:

用XStream解析第三方的xml时,如果第三方xml有变动,只要增加一个节点属性,使用XStream解析时就会报错,而且非常难找错误。fastjson的设计就比XStream设计的要合理,没有的字段不解析出来就可以了。

解决办法:

定制XStream的ReflectionConverter转换器,忽略没有找到的属性域。

二、解决办法

1. 定制XStream的ReflectionConverter转换器,忽略没有找到的属性域:

/**
 * 定制XStream的ReflectionConverter转换器,忽略没有找到的属性域
 *
 * @author Kevin
 * @date 2017-01-04
 * @see ReflectionConverter
 */
public class CustomizedReflectionConverter implements Converter,Caching {

    private static final Class eventHandlerType = JVM.loadClassForName("java.beans.EventHandler");
    private Class<?> type;

    protected final ReflectionProvider reflectionProvider;
    protected final Mapper mapper;
    protected transient SerializationMembers serializationMembers;
    private transient ReflectionProvider pureJavaReflectionProvider;

    public CustomizedReflectionConverter(Mapper mapper,ReflectionProvider reflectionProvider) {
        this.mapper = mapper;
        this.reflectionProvider = reflectionProvider;
        serializationMembers = new SerializationMembers();
    }

    public CustomizedReflectionConverter(Mapper mapper,ReflectionProvider reflectionProvider,Class<?> type) {
        this(mapper,reflectionProvider);
        this.type = type;
    }
    
    protected boolean canAccess(Class<?> type) {
        try {
            reflectionProvider.getFieldOrNull(type,"%");
            return true;
        } catch (NoClassDefFoundError e) {
            e.printStackTrace();
        }
        return false;
    }

    public void marshal(Object original,final HierarchicalStreamWriter writer,final MarshallingContext context) {
        final Object source = serializationMembers.callWriteReplace(original);

        if (source != original && context instanceof ReferencingMarshallingContext) {
            ((ReferencingMarshallingContext)context).replace(original,source);
        }
        if (source.getClass() != original.getClass()) {
            String attributeName = mapper.aliasForSystemAttribute("resolves-to");
            if (attributeName != null) {
                writer.addAttribute(attributeName,mapper.serializedClass(source.getClass()));
            }
            context.convertAnother(source);
        } else {
            doMarshal(source,writer,context);
        }
    }

    protected void doMarshal(final Object source,final MarshallingContext context) {
        final List fields = new ArrayList();
        final Map defaultFieldDefinition = new HashMap();

        // Attributes might be preferred to child elements ...
        reflectionProvider.visitSerializableFields(source,new ReflectionProvider.Visitor() {
            final Set writtenAttributes = new HashSet();

            public void visit(String fieldName,Class type,Class definedIn,Object value) {
                if (!mapper.shouldSerializeMember(definedIn,fieldName)) {
                    return;
                }
                if (!defaultFieldDefinition.containsKey(fieldName)) {
                    Class lookupType = source.getClass();
                    // See XSTR-457 and OmitFieldsTest
                    if (definedIn != source.getClass()
                        && !mapper.shouldSerializeMember(lookupType,fieldName)) {
                        lookupType = definedIn;
                    }
                    defaultFieldDefinition.put(
                        fieldName,reflectionProvider.getField(lookupType,fieldName));
                }

                SingleValueConverter converter = mapper.getConverterFromItemType(
                    fieldName,type,definedIn);
                if (converter != null) {
                    final String attribute = mapper.aliasForAttribute(mapper.serializedMember(
                        definedIn,fieldName));
                    if (value != null) {
                        if (writtenAttributes.contains(fieldName)) {
                            throw new ConversionException("Cannot write field with name '"
                                + fieldName
                                + "' twice as attribute for object of type "
                                + source.getClass().getName());
                        }
                        final String str = converter.toString(value);
                        if (str != null) {
                            writer.addAttribute(attribute,str);
                        }
                    }
                    writtenAttributes.add(fieldName);
                } else {
                    fields.add(new FieldInfo(fieldName,definedIn,value));
                }
            }
        });

        new Object() {
            {
                for (Iterator fieldIter = fields.iterator(); fieldIter.hasNext();) {
                    FieldInfo info = (FieldInfo)fieldIter.next();
                    if (info.value != null) {
                        Mapper.ImplicitCollectionMapping mapping = mapper
                            .getImplicitCollectionDefForFieldName(
                                source.getClass(),info.fieldName);
                        if (mapping != null) {
                            if (context instanceof ReferencingMarshallingContext) {
                                if (info.value != Collections.EMPTY_LIST
                                    && info.value != Collections.EMPTY_SET
                                    && info.value != Collections.EMPTY_MAP) {
                                    ReferencingMarshallingContext refContext = (ReferencingMarshallingContext)context;
                                    refContext.registerImplicit(info.value);
                                }
                            }
                            final boolean isCollection = info.value instanceof Collection;
                            final boolean isMap = info.value instanceof Map;
                            final boolean isEntry = isMap && mapping.getKeyFieldName() == null;
                            final boolean isArray = info.value.getClass().isArray();
                            for (Iterator iter = isArray
                                ? new ArrayIterator(info.value)
                                : isCollection ? ((Collection)info.value).iterator() : isEntry
                                    ? ((Map)info.value).entrySet().iterator()
                                    : ((Map)info.value).values().iterator(); iter.hasNext();) {
                                Object obj = iter.next();
                                final String itemName;
                                final Class itemType;
                                if (obj == null) {
                                    itemType = Object.class;
                                    itemName = mapper.serializedClass(null);
                                } else if (isEntry) {
                                    final String entryName = mapping.getItemFieldName() != null
                                        ? mapping.getItemFieldName()
                                        : mapper.serializedClass(Map.Entry.class);
                                    Map.Entry entry = (Map.Entry)obj;
                                    ExtendedHierarchicalStreamWriterHelper.startNode(
                                        writer,entryName,entry.getClass());
                                    writeItem(entry.getKey(),context,writer);
                                    writeItem(entry.getValue(),writer);
                                    writer.endNode();
                                    continue;
                                } else if (mapping.getItemFieldName() != null) {
                                    itemType = mapping.getItemType();
                                    itemName = mapping.getItemFieldName();
                                } else {
                                    itemType = obj.getClass();
                                    itemName = mapper.serializedClass(itemType);
                                }
                                writeField(
                                    info.fieldName,itemName,itemType,info.definedIn,obj);
                            }
                        } else {
                            writeField(
                                info.fieldName,null,info.type,info.value);
                        }
                    }
                }

            }

            void writeField(String fieldName,String aliasName,Class fieldType,Object newObj) {
                Class actualType = newObj != null ? newObj.getClass() : fieldType;
                ExtendedHierarchicalStreamWriterHelper.startNode(writer,aliasName != null
                    ? aliasName
                    : mapper.serializedMember(source.getClass(),fieldName),actualType);

                if (newObj != null) {
                    Class defaultType = mapper.defaultImplementationOf(fieldType);
                    if (!actualType.equals(defaultType)) {
                        String serializedClassName = mapper.serializedClass(actualType);
                        if (!serializedClassName.equals(mapper.serializedClass(defaultType))) {
                            String attributeName = mapper.aliasForSystemAttribute("class");
                            if (attributeName != null) {
                                writer.addAttribute(attributeName,serializedClassName);
                            }
                        }
                    }

                    final Field defaultField = (Field)defaultFieldDefinition.get(fieldName);
                    if (defaultField.getDeclaringClass() != definedIn) {
                        String attributeName = mapper.aliasForSystemAttribute("defined-in");
                        if (attributeName != null) {
                            writer.addAttribute(
                                attributeName,mapper.serializedClass(definedIn));
                        }
                    }

                    Field field = reflectionProvider.getField(definedIn,fieldName);
                    marshallField(context,newObj,field);
                }
                writer.endNode();
            }

            void writeItem(Object item,MarshallingContext context,HierarchicalStreamWriter writer) {
                if (item == null) {
                    String name = mapper.serializedClass(null);
                    ExtendedHierarchicalStreamWriterHelper.startNode(
                        writer,name,Mapper.Null.class);
                    writer.endNode();
                } else {
                    String name = mapper.serializedClass(item.getClass());
                    ExtendedHierarchicalStreamWriterHelper.startNode(
                        writer,item.getClass());
                    context.convertAnother(item);
                    writer.endNode();
                }
            }
        };
    }

    protected void marshallField(final MarshallingContext context,Object newObj,Field field) {
        context.convertAnother(
            newObj,mapper.getLocalConverter(field.getDeclaringClass(),field.getName()));
    }

    public Object unmarshal(final HierarchicalStreamReader reader,final UnmarshallingContext context) {
        Object result = instantiateNewInstance(reader,context);
        result = doUnmarshal(result,reader,context);
        return serializationMembers.callReadResolve(result);
    }

    @SuppressWarnings("serial")
	public Object doUnmarshal(final Object result,final HierarchicalStreamReader reader,final UnmarshallingContext context) {
        final Class resultType = result.getClass();
        final Set seenFields = new HashSet() {
            public boolean add(Object e) {
                if (!super.add(e)) {
                    throw new DuplicateFieldException(((FastField)e).getName());
                }
                return true;
            }
        };

        // process attributes before recursing into child elements.
        Iterator it = reader.getAttributeNames();
        while (it.hasNext()) {
            String attrAlias = (String)it.next();
            String attrName = mapper
                .realMember(resultType,mapper.attributeForAlias(attrAlias));
            Field field = reflectionProvider.getFieldOrNull(resultType,attrName);
            if (field != null && shouldUnmarshalField(field)) {
                Class classDefiningField = field.getDeclaringClass();
                if (!mapper.shouldSerializeMember(classDefiningField,attrName)) {
                    continue;
                }
                
                // we need a converter that produces a string representation only
                SingleValueConverter converter = mapper.getConverterFromAttribute(
                    classDefiningField,attrName,field.getType());
                Class type = field.getType();
                if (converter != null) {
                    Object value = converter.fromString(reader.getAttribute(attrAlias));
                    if (type.isPrimitive()) {
                        type = Primitives.box(type);
                    }
                    if (value != null && !type.isAssignableFrom(value.getClass())) {
                        throw new ConversionException("Cannot convert type "
                            + value.getClass().getName()
                            + " to type "
                            + type.getName());
                    }
                    seenFields.add(new FastField(classDefiningField,attrName));
                    reflectionProvider.writeField(result,value,classDefiningField);
                }
            }
        }

        Map implicitCollectionsForCurrentObject = null;
        while (reader.hasMoreChildren()) {
            reader.moveDown();

            String originalNodeName = reader.getNodeName();
            Class explicitDeclaringClass = readDeclaringClass(reader);
            Class fieldDeclaringClass = explicitDeclaringClass == null
                ? resultType
                : explicitDeclaringClass;
            String fieldName = mapper.realMember(fieldDeclaringClass,originalNodeName);
            Mapper.ImplicitCollectionMapping implicitCollectionMapping = mapper
                .getImplicitCollectionDefForFieldName(fieldDeclaringClass,fieldName);
            final Object value;
            String implicitFieldName = null;
            Field field = null;
            Class type = null;
            if (implicitCollectionMapping == null) {
                // no item of an implicit collection for this name ... do we have a field?
                field = reflectionProvider.getFieldOrNull(fieldDeclaringClass,fieldName);
                if (field == null) {
                    // it is not a field ... do we have a field alias?
                    Class itemType = mapper.getItemTypeForItemFieldName(resultType,fieldName);
                    if (itemType != null) {
                        String classAttribute = HierarchicalStreams.readClassAttribute(
                            reader,mapper);
                        if (classAttribute != null) {
                            type = mapper.realClass(classAttribute);
                        } else {
                            type = itemType;
                        }
                    } else {
                        // it is not an alias ... do we have an element of an implicit
                        // collection based on type only?
                        try {
                            type = mapper.realClass(originalNodeName);
                            implicitFieldName = mapper.getFieldNameForItemTypeAndName(
                                context.getRequiredType(),originalNodeName);
                        } catch (CannotResolveClassException e) {
                            e.printStackTrace();
                        }
                        if (type == null || (type != null && implicitFieldName == null)) {
                            type = null;
                        }
                    }
                    if (type == null) {
                        // no type,no value
                        value = null;
                    } else {
                        if (Map.Entry.class.equals(type)) {
                            // it is an element of an implicit map with two elements now for
                            // key and value 
                            reader.moveDown();
                            final Object key = context.convertAnother(
                                result,HierarchicalStreams.readClassType(reader,mapper));
                            reader.moveUp();
                            reader.moveDown();
                            final Object v = context.convertAnother(
                                result,mapper));
                            reader.moveUp();
                            value = Collections.singletonMap(key,v)
                                .entrySet().iterator().next();
                        } else {
                            // recurse info hierarchy
                            value = context.convertAnother(result,type);
                        }
                    }
                } else {
                    boolean fieldAlreadyChecked = false;
                    
                    // we have a field,but do we have to address a hidden one?
                    if (explicitDeclaringClass == null) {
                        while (field != null
                            && !(fieldAlreadyChecked = shouldUnmarshalField(field)
                                && mapper.shouldSerializeMember(
                                    field.getDeclaringClass(),fieldName))) {
                            field = reflectionProvider.getFieldOrNull(field
                                .getDeclaringClass()
                                .getSuperclass(),fieldName);
                        }
                    }
                    if (field != null
                        && (fieldAlreadyChecked || (shouldUnmarshalField(field) && mapper
                            .shouldSerializeMember(field.getDeclaringClass(),fieldName)))) {

                        String classAttribute = HierarchicalStreams.readClassAttribute(
                            reader,mapper);
                        if (classAttribute != null) {
                            type = mapper.realClass(classAttribute);
                        } else {
                            type = mapper.defaultImplementationOf(field.getType());
                        }
                        value = unmarshallField(context,result,field);
                        Class definedType = field.getType();
                        if (!definedType.isPrimitive()) {
                            type = definedType;
                        }
                    } else {
                        value = null;
                    }
                }
            } else {
                // we have an implicit collection with defined names
                implicitFieldName = implicitCollectionMapping.getFieldName();
                type = implicitCollectionMapping.getItemType();
                if (type == null) {
                    String classAttribute = HierarchicalStreams.readClassAttribute(
                        reader,mapper);
                    type = mapper.realClass(classAttribute != null
                        ? classAttribute
                        : originalNodeName);
                }
                value = context.convertAnother(result,type);
            }

            if (value != null && !type.isAssignableFrom(value.getClass())) {
                throw new ConversionException("Cannot convert type "
                    + value.getClass().getName()
                    + " to type "
                    + type.getName());
            }

            if (field != null) {
                reflectionProvider.writeField(result,fieldName,field.getDeclaringClass());
                seenFields.add(new FastField(field.getDeclaringClass(),fieldName));
            } else if (type != null) {
                if (implicitFieldName == null) {
                    // look for implicit field
                    implicitFieldName = mapper.getFieldNameForItemTypeAndName(
                        context.getRequiredType(),value != null ? value.getClass() : Mapper.Null.class,originalNodeName);
                }
                if (implicitCollectionsForCurrentObject == null) {
                    implicitCollectionsForCurrentObject = new HashMap();
                }
                writeValueToImplicitCollection(
                    value,implicitCollectionsForCurrentObject,implicitFieldName);
            }

            reader.moveUp();
        }

        if (implicitCollectionsForCurrentObject != null) {
            for (Iterator iter = implicitCollectionsForCurrentObject.entrySet().iterator(); iter
                .hasNext();) {
                Map.Entry entry = (Map.Entry)iter.next();
                Object value = entry.getValue();
                if (value instanceof ArraysList) {
                    Object array = ((ArraysList)value).toPhysicalArray();
                    reflectionProvider.writeField(result,(String)entry.getKey(),array,null);
                }
            }
        }

        return result;
    }

    protected Object unmarshallField(final UnmarshallingContext context,final Object result,Field field) {
        return context.convertAnother(
            result,field.getName()));
    }

    protected boolean shouldUnmarshalTransientFields() {
        return false;
    }

    protected boolean shouldUnmarshalField(Field field) {
        return !(Modifier.isTransient(field.getModifiers()) && !shouldUnmarshalTransientFields());
    }

    private void writeValueToImplicitCollection(Object value,Map implicitCollections,Object result,String implicitFieldName) {
        Collection collection = (Collection)implicitCollections.get(implicitFieldName);
        if (collection == null) {
            Class physicalFieldType = reflectionProvider.getFieldType(
                result,implicitFieldName,null);
            if (physicalFieldType.isArray()) {
                collection = new ArraysList(physicalFieldType);
            } else {
                Class fieldType = mapper.defaultImplementationOf(physicalFieldType);
                if (!(Collection.class.isAssignableFrom(fieldType) || Map.class
                    .isAssignableFrom(fieldType))) {
                    throw new ObjectAccessException(
                        "Field "
                            + implicitFieldName
                            + " of "
                            + result.getClass().getName()
                            + " is configured for an implicit Collection or Map,but field is of type "
                            + fieldType.getName());
                }
                if (pureJavaReflectionProvider == null) {
                    pureJavaReflectionProvider = new PureJavaReflectionProvider();
                }
                Object instance = pureJavaReflectionProvider.newInstance(fieldType);
                if (instance instanceof Collection) {
                    collection = (Collection)instance;
                } else {
                    Mapper.ImplicitCollectionMapping implicitCollectionMapping = mapper
                        .getImplicitCollectionDefForFieldName(result.getClass(),implicitFieldName);
                    collection = new MappingList(
                        (Map)instance,implicitCollectionMapping.getKeyFieldName());
                }
                reflectionProvider.writeField(result,instance,null);
            }
            implicitCollections.put(implicitFieldName,collection);
        }
        collection.add(value);
    }

    private Class readDeclaringClass(HierarchicalStreamReader reader) {
        String attributeName = mapper.aliasForSystemAttribute("defined-in");
        String definedIn = attributeName == null ? null : reader.getAttribute(attributeName);
        return definedIn == null ? null : mapper.realClass(definedIn);
    }

    protected Object instantiateNewInstance(HierarchicalStreamReader reader,UnmarshallingContext context) {
        String attributeName = mapper.aliasForSystemAttribute("resolves-to");
        String readResolveValue = attributeName == null ? null : reader
            .getAttribute(attributeName);
        Object currentObject = context.currentObject();
        if (currentObject != null) {
            return currentObject;
        } else if (readResolveValue != null) {
            return reflectionProvider.newInstance(mapper.realClass(readResolveValue));
        } else {
            return reflectionProvider.newInstance(context.getRequiredType());
        }
    }

    public void flushCache() {
        serializationMembers.flushCache();
    }

    @SuppressWarnings("serial")
	public static class DuplicateFieldException extends ConversionException {
        public DuplicateFieldException(String msg) {
            super("Duplicate field " + msg);
            add("field",msg);
        }
    }

    @SuppressWarnings("serial")
	public static class UnknownFieldException extends ConversionException {
        public UnknownFieldException(String type,String field) {
            super("No such field " + type + "." + field);
            add("field",field);
        }
    }

    private static class FieldInfo {
        final String fieldName;
        final Class type;
        final Class definedIn;
        final Object value;

        FieldInfo(String fieldName,Object value) {
            this.fieldName = fieldName;
            this.type = type;
            this.definedIn = definedIn;
            this.value = value;
        }
    }

    @SuppressWarnings("serial")
	private static class ArraysList extends ArrayList {
        final Class physicalFieldType;

        ArraysList(Class physicalFieldType) {
            this.physicalFieldType = physicalFieldType;
        }

        Object toPhysicalArray() {
            Object[] objects = toArray();
            Object array = Array.newInstance(
                physicalFieldType.getComponentType(),objects.length);
            if (physicalFieldType.getComponentType().isPrimitive()) {
                for (int i = 0; i < objects.length; ++i) {
                    Array.set(array,i,Array.get(objects,i));
                }
            } else {
                System.arraycopy(objects,objects.length);
            }
            return array;
        }
    }

    private class MappingList extends AbstractList {

        private final Map map;
        private final String keyFieldName;
        private final Map fieldCache = new HashMap();

        public MappingList(Map map,String keyFieldName) {
            this.map = map;
            this.keyFieldName = keyFieldName;
        }

        public boolean add(Object object) {
            if (object == null) {
                boolean containsNull = !map.containsKey(null);
                map.put(null,null);
                return containsNull;
            }
            Class itemType = object.getClass();

            if (keyFieldName != null) {
                Field field = (Field)fieldCache.get(itemType);
                if (field == null) {
                    field = reflectionProvider.getField(itemType,keyFieldName);
                    fieldCache.put(itemType,field);
                }
                if (field != null) {
                    try {
                        Object key = field.get(object);
                        return map.put(key,object) == null;
                    } catch (IllegalArgumentException e) {
                        e.printStackTrace();
                        throw new ObjectAccessException("Could not get field "
                            + field.getClass()
                            + "."
                            + field.getName(),e);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                        throw new ObjectAccessException("Could not get field "
                            + field.getClass()
                            + "."
                            + field.getName(),e);
                    }
                }
            } else if (object instanceof Map.Entry) {
                final Map.Entry entry = (Map.Entry)object;
                return map.put(entry.getKey(),entry.getValue()) == null;
            }

            throw new ConversionException("Element of type "
                + object.getClass().getName()
                + " is not defined as entry for map of type "
                + map.getClass().getName());
        }

        public Object get(int index) {
            throw new UnsupportedOperationException();
        }

        public int size() {
            return map.size();
        }
    }

	public boolean canConvert(Class type) {
        return ((this.type != null && this.type == type) || (this.type == null && type != null && type != eventHandlerType))
            && canAccess(type);
    }
    
}

其实就是去掉XStream中AbstractReflectionConverter.java类中处理未知属性的相关代码,如:

private void handleUnknownField(Class classDefiningField,String fieldName,Class resultType,String originalNodeName) {
    if (classDefiningField == null) {
        for (Class cls = resultType; cls != null; cls = cls.getSuperclass()) {
            if (!mapper.shouldSerializeMember(cls,originalNodeName)) {
                return;
            }
        }
    }
   throw new UnknownFieldException(resultType.getName(),fieldName);
}

2. 简单封装XStream:

/**
 * xml工具
 *
 * @author Kevin
 * @date 2017-01-04
 */
public class XmlUtil {

    private static final String XML_HEAD = "<?xml version='1.0' encoding='UTF-8'?>";

    public static String object2Xml(Object object) {
        XStream xstream = new XStream(new XppDriver(new NoNameCoder()));
        xstream.autodetectAnnotations(true);
        String xml = xstream.toXML(object);
        return XML_HEAD + xml;
    }

    public static <T> T xml2Object(String responseString,Class<T> clazz) {
        XStream xstream = new XStream(new XppDriver(new NoNameCoder()));
        xstream.autodetectAnnotations(true);
        String alias;
        XStreamAlias xStreamAlias = clazz.getAnnotation(XStreamAlias.class);
        if (xStreamAlias == null) {
            alias = clazz.getName();
        } else {
            alias = xStreamAlias.value();
        }
        xstream.alias(alias,clazz);
        xstream.registerConverter(new CustomizedReflectionConverter(xstream.getMapper(),xstream.getReflectionProvider()),XStream.PRIORITY_LOW);
        Object object = xstream.fromXML(responseString);
        return (T) object;
    }

}

3. 测试代码:

/**
 * 简单封装XStream的XmlUtil基本使用示例
 *
 * @author Kevin
 * @date 2017-01-04
 */
public class XStreamDemo {

    public static void main(String[] args) {

        People people = new People();
        people.setId(1L);
        people.setName("Kevin");
        people.setCompanyName("暂时不告诉你");
        people.setCity(new City("杭州"));

        // Serializing an object to XML
        String peopleXML = XmlUtil.object2Xml(people);
        System.out.println("peopleXML is : \n" + peopleXML);

        //peopleXML的内容为:
       /*
       <People>
          <name>Kevin</name>
          <company_name>暂时不告诉你</company_name>
          <City>
            <name>杭州</name>
          </City>
        </People>
        */

        // Deserializing an object back from XML
        People peopleTemp = XmlUtil.xml2Object(peopleXML,People.class);
        System.out.println("city name is : " + peopleTemp.getCity().getName());
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


php输出xml格式字符串
J2ME Mobile 3D入门教程系列文章之一
XML轻松学习手册
XML入门的常见问题(一)
XML入门的常见问题(三)
XML轻松学习手册(2)XML概念
xml文件介绍及使用
xml编程(一)-xml语法
XML文件结构和基本语法
第2章 包装类
XML入门的常见问题(二)
Java对象的强、软、弱和虚引用
JS解析XML文件和XML字符串详解
java中枚举的详细使用介绍
了解Xml格式
XML入门的常见问题(四)
深入SQLite多线程的使用总结详解
PlayFramework完整实现一个APP(一)
XML和YAML的使用方法
XML轻松学习总节篇