2013-08-02 42 views
1

我正在使用XStream。目前,用其他方法替换XStream并不容易。我有一个接口(MyInterface)和实现该接口的几个子类(在下面的示例代码中,有一个名为MyImplementation)。如何使用XStream对类型层次结构中的对象进行序列化/反序列化?

我想序列化和反序列化子类的实例。我发现,如果我把类属性到XML我可以反序列化就好了:

<myInterfaceElement class="myPackage.MyImplementation"> 
    <field1>value1</field1> 
    <field2>value2</field2> 
</myInterfaceElement> 

不过,我不知道怎么弄的XStream写的类属性。如何在序列化时让XStream包含类属性?还是有另一种序列化/反序列化类层次结构的方法,以便所有实现的元素名称相同,并且每个子类可以定义自己的字段?

下面是MyInterface,MyImplementation,一个试图使它工作的JUnit测试用例的例子。 deserializeWithClassAttribute测试在classAttributeSetInResult失败时通过。


package myPackage; 

public interface MyInterface { 

} 

package myPackage; 

public class MyImplementation implements MyInterface { 
    public String field1; 
    public String field2; 

    public MyImplementation(String field1, String field2) { 
     this.field1 = field1; 
     this.field2 = field2; 
    } 
} 

package myPackage; 
import org.junit.Test; 

import com.thoughtworks.xstream.XStream; 
import com.thoughtworks.xstream.io.xml.DomDriver; 

import static org.junit.Assert.*; 


public class xstreamTest { 
    @Test 
    public void classAttributeSetInResult() { 
     MyInterface objectToSerialize = new MyImplementation("value1", "value2"); 

     final XStream xStream = new XStream(new DomDriver()); 
     xStream.alias("myInterfaceElement", MyInterface.class); 

     String xmlResult = xStream.toXML(objectToSerialize).toString(); 

     String expectedResult = 
"<myInterfaceElement class=\"myPackage.MyImplementation\">\n" + 
" <field1>value1</field1>\n" + 
" <field2>value2</field2>\n" + 
"</myInterfaceElement>"; 

     assertEquals(expectedResult, xmlResult); 
    } 

    @Test 
    public void deserializeWithClassAttribute() { 
     String inputXmlString = 
"<myInterfaceElement class=\"myPackage.MyImplementation\">\r\n" + 
" <field1>value1</field1>\r\n" + 
" <field2>value2</field2>\r\n" + 
"</myInterfaceElement>"; 

     final XStream xStream = new XStream(new DomDriver()); 

     MyInterface result = (MyInterface)xStream.fromXML(inputXmlString); 
     assertTrue("Instance of MyImplementation returned", result instanceof MyImplementation); 

     MyImplementation resultAsMyImplementation = (MyImplementation)result; 
     assertEquals("Field 1 deserialized", "value1", resultAsMyImplementation.field1); 
     assertEquals("Field 2 deserialized", "value2", resultAsMyImplementation.field2); 
    } 
} 

回答

1

我想通了这一点通过以下操作(感谢麦当劳的提示使用a转换器):

  1. 添加扩展ReflectionConverter的自定义Converter:

    package myPackage; 
    
    import com.thoughtworks.xstream.converters.MarshallingContext; 
    import com.thoughtworks.xstream.converters.reflection.ReflectionConverter; 
    import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; 
    import com.thoughtworks.xstream.io.HierarchicalStreamWriter; 
    import com.thoughtworks.xstream.mapper.Mapper; 
    
    public class MyInterfaceConverter extends ReflectionConverter { 
    
        public MyInterfaceConverter(Mapper mapper, ReflectionProvider reflectionProvider) { 
         super(mapper, reflectionProvider); 
        } 
    
        @Override 
        public void marshal(Object original, final HierarchicalStreamWriter writer, final MarshallingContext context) { 
         writer.addAttribute("class", original.getClass().getCanonicalName()); 
         super.marshal(original, writer, context); 
        } 
    
        @SuppressWarnings("rawtypes") 
        @Override 
        public boolean canConvert(Class type) { 
         return MyInterface.class.isAssignableFrom(type); 
        } 
    
    } 
    
  2. 注册新的转换器,当我设置XSTREAM:

    @Test 
    public void classAttributeSetInResult() { 
        MyInterface objectToSerialize = new MyImplementation("value1", "value2"); 
    
        final XStream xStream = new XStream(new DomDriver()); 
        xStream.alias("myInterfaceElement", MyImplementation.class); 
        xStream.registerConverter(new MyInterfaceConverter(xStream.getMapper(), xStream.getReflectionProvider())); 
    
        String xmlResult = xStream.toXML(objectToSerialize).toString(); 
    
        String expectedResult = 
         "<myInterfaceElement class=\"myPackage.MyImplementation\">\n" + 
         " <field1>value1</field1>\n" + 
         " <field2>value2</field2>\n" + 
         "</myInterfaceElement>"; 
    
        assertEquals(expectedResult, xmlResult); 
    } 
    

希望这会帮助别人的道路。如果有人有更好的主意,请告诉我!

+0

当然,你是对的。使用像这些简单类的“标准”XStream序列化机制要好得多。 我从我需要定制转换器的更复杂的类中选择我的示例。 – MrD

+0

好主意。我发现手动添加类名作为属性会产生一个异常,因为它已经被XStream自动添加了。将编辑您的答案以包含解组方法的示例。 – ScarOnTheSky

0

我会使用一个自定义的转换器来解决这个问题:

你的类/接口:

public static interface MyInterface { 
    public String getField1(); 
    public String getField2(); 
} 

public static class MyImplementation implements MyInterface { 
    public String field1; 
    public String field2; 

    public MyImplementation(String field1, String field2) { 
     this.field1 = field1; 
     this.field2 = field2; 
    } 

    public String getField1() { return field1; } 
    public String getField2() { return field2; } 
} 

相对较快速的&脏转换器:

public static class MyInterfaceConverter implements Converter { 
    private static final String ATTR_NAME_CLASS = "concrete-class"; 
    private static final String NODE_NAME_FIELD1 = "field1"; 
    private static final String NODE_NAME_FIELD2 = "field2"; 

    public boolean canConvert(Class type) 
    { 
     return type.equals(MyImplementation.class); 
    } 

    public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) 
    { 
     if (obj == null) 
      // no need to save null-objects 
      return; 

     final String type = obj.getClass().getSimpleName(); 
     final MyInterface myInterface; 
     if (obj instanceof MyImplementation) 
      myInterface = (MyInterface) obj; 
     // else if (...) 
     //  ... 
     else 
      throw new IllegalArgumentException("Cannot convert objects of type " + obj.getClass()); 

     writer.addAttribute(ATTR_NAME_CLASS, type); 
     marshalAttribute(writer, context, NODE_NAME_FIELD1, myInterface.getField1()); 
     marshalAttribute(writer, context, NODE_NAME_FIELD2, myInterface.getField2()); 
    } 

    private static void marshalAttribute(HierarchicalStreamWriter writer, MarshallingContext context, String attrName, Object val) 
    { 
     if (val != null) { 
      writer.startNode(attrName); 
      context.convertAnother(val); 
      writer.endNode(); 
     } 
    } 

    public Object unmarshal(HierarchicalStreamReader reader, 
      UnmarshallingContext context) 
    { 
     final String type = reader.getAttribute(ATTR_NAME_CLASS); 
     String field1Value = null, field2Value = null; 
     while (reader.hasMoreChildren()) { 
      reader.moveDown(); 
      if (NODE_NAME_FIELD1.equals(reader.getNodeName())) 
       field1Value = (String)context.convertAnother(null, String.class); 
      else if (NODE_NAME_FIELD2.equals(reader.getNodeName())) 
       field2Value = (String)context.convertAnother(null, String.class); 
      reader.moveUp(); 
     } 

     if (MyImplementation.class.getSimpleName().equals(type)) { 
      return new MyImplementation(field1Value, field2Value); 
     } 
     throw new IllegalArgumentException("Cannot unmarshal objects of type " + type); 
    } 
} 

测试/使用/ XStream的初始化:

@Test 
public void classAttributeSetInResult() { 
    MyInterface objectToSerialize = new MyImplementation("value1", "value2"); 

    final XStream xStream = new XStream(new DomDriver()); 
    xStream.alias("myInterfaceElement", MyImplementation.class); 
    // xStream.alias("myInterfaceElement", OtherImplementation.class); 
    xStream.registerConverter(new MyInterfaceConverter()); 

    String xmlResult = xStream.toXML(objectToSerialize).toString(); 

    String expectedResult = 
"<myInterfaceElement concrete-class=\"MyImplementation\">\r\n" + 
" <field1>value1</field1>\r\n" + 
" <field2>value2</field2>\r\n" + 
"</myInterfaceElement>"; 

    assertEquals(expectedResult, xmlResult); 
} 

@Test 
public void deserializeWithClassAttribute() { 
    String inputXmlString = 
"<myInterfaceElement concrete-class=\"MyImplementation\">\r\n" + 
" <field1>value1</field1>\r\n" + 
" <field2>value2</field2>\r\n" + 
"</myInterfaceElement>"; 

    final XStream xStream = new XStream(new DomDriver()); 
    xStream.alias("myInterfaceElement", MyImplementation.class); 
    // xStream.alias("myInterfaceElement", OtherImplementation.class); 
    xStream.registerConverter(new MyInterfaceConverter()); 

    MyInterface result = (MyInterface)xStream.fromXML(inputXmlString); 
    assertTrue("Instance of MyImplementation returned", result instanceof MyImplementation); 

    MyImplementation resultAsMyImplementation = (MyImplementation)result; 
    assertEquals("Field 1 deserialized", "value1", resultAsMyImplementation.field1); 
    assertEquals("Field 2 deserialized", "value2", resultAsMyImplementation.field2); 
} 
+0

谢谢MrD。这看起来像是可以工作的,如果对MyInterface接口的每个新实现都修改MyInterfaceConverter。由于每个实现都可以拥有自己的字段属性,这也意味着MyInterfaceConverter需要关于所有这些实现的深入知识。我认为这不符合我的需求。我想知道是否有办法通过多一点思考来做到这一点?但是,如果我加入这些反思,是不是真的重复了XStream应该为我做的事情? – dennislloydjr

相关问题