反序列化包含一些不可反序列化的对象的数组(抢救可反序列化的部分)

时间:2014-01-03 22:51:11

标签: java arrays serialization

背景

我试图以这样的方式编写对象反序列化:如果一个对象数组包含某些对象(由于代码更改)无法反序列化,那么数组中的那些引用将变为空而不是抛出异常;允许对象的其余部分进行抢救。

我尝试了什么

我试图使用自定义序列化/反序列化,希望我能捕获异常并应用我的自定义“make it null”逻辑。其代码如下。然而,我似乎能够捕获异常的第一点是整个数组反序列化已经失败。

public class AppleHolder implements Serializable{
    Apple[] apples=new Apple[5];
    double otherData=15;


    public AppleHolder(){
        Apple goodApple=new Apple("GoodApple","tastyGood");
        BadApple badApple=new BadApple("BadApple","tastyBad");

        apples[0]=goodApple;
        apples[1]=goodApple; // multiple references to same object intentional
        apples[2]=goodApple;
        apples[3]=badApple;
        apples[4]=badApple;
    }
    private void writeObject(ObjectOutputStream o)
            throws IOException {

        o.writeObject(apples);
        o.writeObject(otherData);
    }

    private void readObject(ObjectInputStream o)
            throws IOException, ClassNotFoundException {

        apples = (Apple[]) o.readObject();
        otherData = (double) o.readObject();
    }



    public static void main(String[] args)
            throws Exception {

        /*
         * (1) First run serialize()
         * (2) Change the badApple's serialVersionUID to 2
         * (3) Run deSerialize(()
         */

        serialize();


        //deSerialize();

    }

    public static void serialize() throws Exception{
        AppleHolder testWrite = new AppleHolder();
        FileOutputStream fos = new FileOutputStream("testfile");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(testWrite);
        oos.flush();
        oos.close();

    }

    public static void deSerialize() throws Exception{
        AppleHolder testRead;
        FileInputStream fis = new FileInputStream("testfile");
        ObjectInputStream ois = new ObjectInputStream(fis);
        testRead = (AppleHolder) ois.readObject();
        ois.close();

        System.out.println("--Read object--");
        System.out.println("propertyOne: " + testRead.apples[0].getPropertyOne());

    }

}

public class Apple implements Serializable {
    private String propertyOne;
    private String propertyTwo;

    public Apple(String propertyOne, String propertyTwo) {
        this.propertyOne = propertyOne;
        this.propertyTwo = propertyTwo;
        validate();
    }

    private void writeObject(ObjectOutputStream o)
            throws IOException {

        o.writeObject(propertyOne);
        o.writeObject(propertyTwo);
    }

    private void readObject(ObjectInputStream o)
            throws IOException, ClassNotFoundException {

        propertyOne = (String) o.readObject();
        propertyTwo = (String) o.readObject();
        validate();
    }

    private void validate(){
        if(propertyOne == null ||
                propertyOne.length() == 0 ||
                propertyTwo == null ||
                propertyTwo.length() == 0){

            throw new IllegalArgumentException();
        }
    }

    public String getPropertyOne() {
        return propertyOne;
    }

    public String getPropertyTwo() {
        return propertyTwo;
    }
}

public class BadApple extends Apple {

    private static final long serialVersionUID = 1;


    public BadApple(String propertyOne, String propertyTwo) {
        super(propertyOne, propertyTwo);
    }


}

我的例外是

Exception in thread "main" java.io.InvalidClassException: customserialisation.BadApple; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1620)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1515)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1769)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1348)
    at java.io.ObjectInputStream.readArray(ObjectInputStream.java:1704)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1342)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
    at customserialisation.AppleHolder.readObject(AppleHolder.java:43)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1017)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1891)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1796)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1348)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
    at customserialisation.AppleHolder.deSerialize(AppleHolder.java:79)
    at customserialisation.AppleHolder.main(AppleHolder.java:61)

我认为我可以通过捕获异常而不是部分apples数组来挽救“otherData”。

我尝试了什么2

根据Alexander Torstling的answer我试图从反序列化的片段中重新创建数组

apples=new Apple[appleCount]
for(int i=0; i<appleCount; i++) {
  try { 
    apples[i]= o.readObject());
  } catch(Exception e) {
    //Add null or nothing or what you want.
    apples[i]=null;
  }
}

但是,这不会消耗badApple中可能包含的任何o.readObject()(在这种情况下是两个字符串),因此反序列化的数据不同步,在我的情况下,我得到一个转换异常,因为otherData = (double) o.readObject();读取应该属于BadApple的String,因为它从未从流中消耗过。

问题

如何打捞序列化数组,其中只有部分对象可反序列化?从而获得具有非可反序列化部分的空条目的数组。在我的数组中,我在一个数组中有几个对同一个对象的引用,在反序列化过程中保留它是至关重要的。

所以进入序列化我有

[GoodApple]
[GoodApple]  
[GoodApple]  
[BadApple]
[BadApple]

我想要反序列化(因为badApple已经改变,无法反序列化

[GoodApple]
[GoodApple]  
[GoodApple]  
[null]
[null]

我希望这能提供一个后备,在这种情况下无法实现向后兼容,或者删除了之前安装的程序的第三方修改

2 个答案:

答案 0 :(得分:1)

我不确定我是否完全理解你的问题,但由于你不想打断数组反序列化,我认为你不能以任何有意义的方式捕获并继续。我看到的其他选项是编写自定义数组反序列化例程或为BadApple编写自定义反序列化器。如果向后兼容很难,那么只是将字段设置为伪值并设置一个表示“错误输入”的标志呢?问题是你无法修改BadApple类吗?

编辑:顺便说一句,如果你不想看看数组反序列化的完成方式,请查看ObjectInputStream #readArray。看起来可以复制和修改代码来支持你的场景,但如果我是你,我会坚持支持旧版本的反序列化,似乎不那么混乱。

EDIT2:我没有能够提出任何真正直接的标准技术来在不编辑BadApple的情况下清除错误。我认为您可能必须使用自定义反序列化来滚动自己的集合序列化,这会在反序列化时跳过坏苹果。如果你直接在AppleHolder中做它会是什么样子的草图(我会用这个功能制作一个单独的列表类型,但我认为这个例子更清晰):

public class AppleHolder implements Serializable{
  static int START_OF_APPLE_MAGIC=120;
  List<Apple> apples=new ArrayList<Apple>();
  double otherData=15;

  private void writeObject(ObjectOutputStream o)
        throws IOException {
    o.writeInt(apples.size());
    for(Apple a: apples) {
      o.write(START_OF_APPLE_MAGIC);
      o.writeObject(a);
    }
    o.writeObject(otherData);
  }

  private void readObject(ObjectInputStream o)
        throws IOException, ClassNotFoundException {
    int appleCount = o.readInt();
    apples = new ArrayList<Apple>(appleCount);
    for(int i=0; i<appleCount; i++) {
      try { 
        while(o.read() != START_OF_APPLE_MAGIC) {
          //fast forward to boundary. Maybe add a max here to avoid infinite loops.
        }
        apples.add((Apple) o.readObject());
      } catch(SomethingWentBadException e) {
        //Add null or nothing or what you want. Look out for failures caused by
        //the value of START_OF_APPLE_MAGIC contained in ordinary fields
        apples.add(null);
      }
    }
    otherData = (double) o.readObject();
  }
}

我使用了一个列表,因为如果你不回读所有对象并且不使用空占位符(未知的确切回读大小)会更容易。该示例应该与数组一起使用,但只需要进行少量调整。

EDIT3:我用魔术边界值更新了示例。这真是个hackish。我使用了一个字节值,否则我们无法确定对象反序列化是否读取了偶数字节(对齐问题)。

答案 1 :(得分:0)

如果您无法更改BadApple并需要在AppleHolder中解决,我认为您运气不好。但如果可以,请查看javadoc for Serializable,特别是:

  

需要在其实例时指定替换的类   从流中读取应该实现这个特殊的方法   确切的签名。

     

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

因此,将此添加到BadApple应该可以解决问题:

private Object readResolve() throws ObjectStreamException { return null; }

编辑:您可能需要明确实施Serializable才能使其正常工作,因此这可能不是您想要的。