一、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()); } }