【问题标题】:Deserializing an array that contains some non-deserializable objects (salvaging the deserializable parts)反序列化包含一些不可反序列化对象的数组(挽救可反序列化的部分)
【发布时间】:2014-01-03 22:51:11
【问题描述】:

背景

我正在尝试以这样一种方式编写对象反序列化:如果对象数组包含 一些 对象(由于代码更改)无法反序列化,那么数组中的那些引用将变为 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]

我希望它提供一个后备方案,在无法实现向后兼容性或对我之前安装的程序的第 3 方修改被删除的情况下

【问题讨论】:

标签: java arrays serialization


【解决方案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:我用一个神奇的边界值更新了这个例子。这真是骇人听闻。我使用了字节值,否则我们无法确定对象反序列化是否读取了偶数字节(对齐问题)。

【讨论】:

  • 我已经澄清了我的问题重新不确定
  • @RichardTingle 我明白了,请查看我的更新。
  • 这似乎对下一个好 o.readObject(); 感到不安。在这种情况下,otherData = (double) o.readObject(); 以异常 java.lang.String cannot be cast to java.lang.Double 结束。我假设是因为每个苹果都包含两个不被 badApple 反序列化过程消耗的字符串,因为坏苹果永远不会被反序列化
  • 是的,我认为你是对的。我环顾四周,找不到任何跳过流中对象的方法。所有的'readFields'和相关方法只能根据API从类本身调用。问题现在转移到检测对象的末端。也许如果您可以在每个对象写入前加上大小,那么您知道要跳过多少字节?我会想一个好办法。
  • @RichardTingle 好的,我可以想出两个不错的策略。一种是在每个“Apple”前面加上一个半唯一的魔术字节序列。当反序列化失败时,逐字节读取,直到达到魔法字节。这应该意味着你在下一个苹果。但这并不能保证,因此您可能会得到误报并处理这些。更正确的版本是在每个苹果前面加上大小,在读取一个苹果之前做mark(),在反序列化失败后reset()和跳过坏苹果。这需要访问底层流,这可以通过各种骇人听闻的方式完成。
【解决方案2】:

如果你无法更改BadApple 并且需要在AppleHolder 中解决它,我认为你很不走运。但如果可以,请查看javadoc for Serializable,尤其是:

当它的实例被替换时需要指定替换的类 从流中读取应该使用 准确的签名。

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

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

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

编辑:您可能需要明确实现 Serializable 才能使其工作,所以这可能不是您想要的。

【讨论】:

  • 这解决了我 50% 的问题,并且可以继续进行下去。有时我控制 badApple,在这些情况下我会使用它
  • 如果这会影响序列化过程而不是反序列化过程(即序列化文件中的替代生命),我可以将其作为编码标准的一部分并解决 100%。我要演戏
  • 我认为readResolve是在反序列化后调用的,所以我认为需要先让readObject不抛出。
  • 查看这个 readResolve() 似乎总是被调用,而不是只有在发生不好的事情时才被调用,所以即使没有发生不好的事情,我的坏苹果最终也会为 null
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-10
  • 2021-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-03
  • 1970-01-01
相关资源
最近更新 更多