Обновление Java-сериализуемого класса - PullRequest
5 голосов
/ 15 февраля 2011

Я читал различные блоги о сериализации и использовании serialVersionUID. Большинство из них упоминают использование его для поддержания состояния сериализуемого класса.

Сценарий, который у меня есть,

Я знаю старый serialVersionUID и новый serialVersionUID.

При чтении в объекте со старым serialVersionUID я хочу манипулировать данными, чтобы они соответствовали новой версии, я хочу беспокоиться об этом, только если объект, в котором я читаю, старого типа.

Это похоже на то, что должно быть очень прямолинейным!

Есть ли способ завладеть serialVersionUID при считывании объекта?

InvalidClassException генерируется до вызова метода readObject в сериализованном классе, поэтому я не могу получить к нему доступ.

Единственный совет, который я нашел, - это переопределить ObjectInputStream, чтобы readClassDescriptor () был доступен, хотя это кажется тяжелым решением для того, что должно быть общей проблемой!

Вся помощь с благодарностью получена!

M

Ответы [ 3 ]

12 голосов
/ 15 февраля 2011

Я собираюсь представить несколько возможных способов поддержки старых версий класса / интерфейса в сериализации:

Использование Migrators

В таком случае вам нужно следующее в вашем Java-проекте:

  1. Неизменяемый интерфейс, объединяющий все эти классы
  2. Старые реализации этого интерфейса аннотированы как @Deprecated
  3. Новая реализация вашего интерфейса
  4. Класс Migrator, помогающий преобразовать устаревший объект в новый

Позвольте мне с самого начала сказать, что не всегда возможно работать с объектами более старых версий (см. Документацию Oracle по версиям сериализуемых объектов ). Для простоты давайте предположим, что при обновлении ваши классы всегда реализуют интерфейс IEntity, который вы определили.

public interface IEntity extends Serializable {
   // your method definitions
}

И предположим, что вы изначально работаете с классом:

public class Entity implements IEntity {
   private static final long serialVersionUID = 123456789L;
   // fields and methods
}

Если вам нужно обновить класс Entity до новой реализации с новым serialVersionUID, то сначала аннотируйте его как @Deprecated, но не переименовывайте его и не перемещайте это в другой пакет :

@Deprecated 
public class Entity implements IEntity {
private static final long serialVersionUID = 123456789L;
  // fields and methods
}

Теперь создайте новую реализацию с новым serialVersionUID (важно) и дополнительным конструктором следующим образом ...

public class Entity_new implements IEntity {
  private static final long serialVersionUID = 5555558L;

  public Entity_new(IEntity){
     // Create a new instance of Entity_new copying the given IEntity
  }

}

Конечно, наиболее важной частью всей процедуры является то, как вы реализуете конструктор выше. Если вы сериализовали некоторые объекты старого типа Entity в виде двоичных файлов (используя, например, ObjectOutputStream), и теперь вы перешли на Entity_new, вы можете проанализировать их как экземпляры Entity и затем преобразовать их в экземпляры Entity_new. Вот пример:

public class Migrator {

   private final IEntity entity;
   private Class<? extends IEntity> newestClass = Entity_new.class;

   public Migrator(final IEntity entity){
    this.entity = entity;
   }

   public Migrator setNewestClass(Class<? extends IEntity> clazz){
     this.newestClass = clazz;
     return this;
   }

   public IEntity migrate() throws Exception {
     Constructor<? extends IEntity> constr =  
        newestClass.getConstructor(IEntity.class);
     return constr.newInstance(this.entity);
   }
}

Есть, конечно, другая альтернатива, которая не требует конкретного конструктора или не использует java отражение . Есть много других дизайнерских подходов, которые можно выбрать. Обратите также внимание на то, что обработка исключений и проверки для нулевых объектов были полностью опущены для простоты в приведенном выше коде.

Разработка универсального сериализуемого интерфейса

Если применимо, вы можете в первую очередь попытаться разработать интерфейс для вашего класса, который вряд ли изменится в будущем. Если вашему классу необходимо хранить набор свойств, которые с большой вероятностью могут быть изменены, рассмотрите возможность использования Map<String, Object> для этой цели, где String относится к имени / идентификатору свойства, а Object - соответствующее значение.

Настройка readObject и writeObject

Существует еще один способ обеспечения поддержки более старой версии, который я упомяну для полноты, но я бы не выбрал его. Вы можете реализовать private void readObject(ObjectInputStream in) и private void writeObject(ObjectOutputStream out) таким образом, чтобы он соответствовал как текущей, так и всем предыдущим версиям вашего класса / интерфейса. Я не уверен, что что-то подобное всегда возможно и устойчиво, и у вас может получиться очень грязная и длительная реализация этих методов.

Альтернативные методы сериализации

Это не отвечает на вопрос ОП, но я считаю, что стоит поднять его. Вы можете рассмотреть сериализацию вашего объекта в некотором формате ASCII, таком как JSON, YAML или XML. В таком случае, если вы сильно не перепроектируете свой сериализуемый интерфейс, расширяемость выходит из коробки. BSON (двоичный JSON) - хороший выбор, если вы ищете расширяемый двоичный протокол. Возможно, это лучший способ обеспечить переносимость ваших объектов между программами, которые могут быть не реализованы в Java.

6 голосов
/ 15 февраля 2011

Вы должны оставить то же самое serialVersionUID. Сериализованные поля не должны совпадать с полями самого класса. Используйте ObjectInputStream.readFields и определите serialPersistentFields (хотя убедитесь, что вы правильно написали).

1 голос
/ 15 февраля 2011

Это не легко сделать. если ваш код может поддерживать оба типа классов, лучший способ справиться с этим - , а не изменить serialVersionUID, но вместо этого определить, являются ли данные новыми или старыми, и прочитать их соответственно.

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

короче говоря, изменение serialVersionUID - это не способ поддержания состояния, это способ указать на несовместимость (т. Е. Ситуация, когда спасение является единственным решением).

...