Как десериализовать класс, если версия класса была изменена - PullRequest
1 голос
/ 09 августа 2011

Я хочу написать код, который может десериализовать класс, даже если класс был изменен (но у вас есть старая версия класса).

Идея проста.

  1. Считайте serialVersionUID сериализованного класса
  2. Убедитесь, что serialVersionUID равно serialVersionUID текущей версии класса.
  3. Еслинет, создайте новый ClassLoader и загрузите старую версию класса в рабочую область.

Я подумал использовать что-то вроде этого:

FileInputStream file = new FileInputStream(filename);
ObjectInputStream o = new ObjectInputStream(file) {
    protected Class<?> resolveClass(java.io.ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        //5639490496474438904L is the suid of the older version
        if (desc.getSerialVersionUID() == 5639490496474438904L) {
            Class c01 = null;
            URL eineURL = new URL("file://localhost/U:/MyJavaProj/bin/test/oldversion/");
            URL[] reiURL = new URL[] {eineURL};
            URLClassLoader clazLader = new URLClassLoader(reiURL);
            c01 = clazLader.loadClass("test.SerObj");
            return c01; 
        }
        return super.resolveClass(desc);
    }
};

SerObj obj = (SerObj) o.readObject();

Проблема в последней строке.Моя текущая версия класса помещена в U: /MyJavaProj/bin/test/SerObj.class Моя старая версия класса помещена в U: / MyJavaProj / bin / test / oldversion / test / SerObj.class

В последней строке я прочитал старую версию класса, но привел ее к новой версии: (*

Есть кто-нибудь идея или может любой другой подход, чтобы добавить поддержку управления версиями длясериализация в Java?

Ответы [ 3 ]

3 голосов
/ 09 августа 2011

Я не знаю, пытаетесь ли вы исправить существующую проблему или написать новую функциональность. если последнее, то я хотел бы изучить использование расширенной функциональности сериализации Java. Сериализация java поддерживает различные средства для возможности обработки нескольких последовательных версий одного и того же класса в одном загрузчике классов (однако, важно, чтобы serialVersionUID оставался одинаковым для всех экземпляров).

вот пример обработки «несовместимого» изменения, где целочисленное значение было изменено на строковое значение:

public class MyClass {
  private static final int CUR_VERSION = 5;
  private String _value;

  private void readObject(ObjectInputStream in) {
    // first value is always the serial version:
    int dataVersion = in.readInt();
    if(dataVersion == CUR_VERSION) {
      // _value is a String
      _value = in.readString();
    } else {
      // in older versions, _value was an int
      _value = String.valueOf(in.readInt());
    }
  }

  private void writeObject(ObjectOutputStream out) {
    // always write version first
    out.writeInt(CUR_VERSION);
    out.writeString(_value);
  }
}
2 голосов
/ 10 августа 2011

Простой ответ на этот вопрос - не меняйте serialVersionUID.

Если вы считаете, что вы внесли несовместимое с сериализацией изменение в класс, сначала прочитайте часть Версии объекта Спецификации сериализации объекта, чтобы дважды проверить это, как вы, вероятно, не сделали, и если это действительно так, измените / добавьте методы writeObject / readObject, чтобы они могли работать со старым форматом сериализации.

И, конечно, не пытайтесь возиться с двумя версиями класса во время выполнения. Это не сработает.

1 голос
/ 09 августа 2011

Вы не сможете выполнить приведение в последней строке, потому что это другой класс (который не является кастуемым) - вы можете получить смутное сообщение, такое как test.SerObj is not an instance of test.SerObj.

Это вытекает из того факта, что класс по сути является экземпляром java.lang.Class, который критически уникален в данном загрузчике классов . Даже если вы ссылались на тот же самый файл *.class, экземпляр SerObj, загруженный загрузчиком классов по умолчанию, отличается от экземпляра SerObj, загруженного clazLader. Ни один класс не может быть приведен к другому.

Таким образом, метод не сможет вернуть SerObj, если вам нужно использовать несколько загрузчиков классов для десериализации. (Кроме того - как это могло быть, когда два файла классов могли иметь произвольные различия?)


Один из возможных обходных путей - определить интерфейс, который определяет поведение, которое фиксируется между версиями класса. Если вы настроите временный загрузчик классов так, чтобы он мог только загружать SerObj из файла класса oldversion и чтобы у него был правильный родительский загрузчик классов, тогда ваш старый SerObj все равно должен реализовывать класс интерфейса как определяется корневым загрузчиком классов.

В этом случае и новый SerObj, и старый SerObj экземпляры можно было бы преобразовать в интерфейс SerObjIface, и поэтому вы можете объявить свой метод для возврата , что . И на самом деле, если вы хотите, чтобы вызывающие абоненты имели дело с разными, но похожими классами (каковыми являются разные версии «одного и того же класса»), вы, возможно, должны в любом случае возвращать интерфейс для этого.

...