Как работать с Java-сериализованным объектом, чей пакет изменился?

У меня есть класс Java, который хранится в объекте HttpSession, который сериализуется и передается между серверами в среде кластера. Для целей этого объяснения позвольте назвать этот класс "Человек".

В процессе улучшения кода этот класс был перемещен из "com.acme.Person" в "com.acme.entity.Person". Внутри класс остается точно таким же (одни и те же поля, одни и те же методы, все одинаковое).

Проблема в том, что у нас есть два набора серверов, на которых запущен старый код и новый код. Серверы со старым кодом имеют сериализованный объект HttpSession, и когда новый код неэтериализует его, он выдает исключение ClassNotFoundException, потому что он не может найти старую ссылку на com.acme.Person. На этом этапе легко справиться с этим, потому что мы можем просто воссоздать объект, используя новый пакет. Затем возникает проблема, связанная с тем, что HttpSession на новых серверах будет сериализовать объект с новой ссылкой на com.acme.entity.Person, и когда это будет неэтериализовано на серверах, на которых запущен старый код, будет выбрано другое исключение. На этом этапе мы больше не можем справляться с этим исключением.

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

Ответы

Ответ 1

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

На самом деле это говорит о том, что вы создаете подкласс ObjectInputStream, который переопределяет метод readClassDescriptor, чтобы сделать что-то вроде этого:

@Override
protected java.io.ObjectStreamClass readClassDescriptor() 
        throws IOException, ClassNotFoundException {
    ObjectStreamClass desc = super.readClassDescriptor();
    if (desc.getName().equals("oldpkg.Widget")) {
        return ObjectStreamClass.lookup(newpkg.Widget.class);
    }
    return desc;
};

Вы также должны посмотреть на этот вопрос SO и его ответы, которые охватывают некоторые из тех же оснований, что и ваш вопрос.

Мой совет: не поддерживать случай, когда старые версии программного обеспечения считывают данные, сериализованные новой версией.

  • Это хорошая возможность поощрять (фактически заставить) людей обновляться до последней версии кода. Вообще говоря, все заинтересованы, что это произойдет раньше, чем позже.

  • Если преждевременно заставить людей обновляться по другим причинам, тогда (IMO) вам следует серьезно подумать о том, чтобы отменить свои изменения в именах классов/пакетов. Подождите, пока у вас нет четкой стратегии/плана обновления, которая 1) технически обоснована и 2) приемлема для всех заинтересованных сторон.

Ответ 2

Это всегда большая головная боль с сериализацией Java. Если вы все равно переносите свои классы, я бы рекомендовал изучить миграцию в другой механизм сериализации, такой как XStream. В JavaLobby есть интересная статья в этом.

Ответ 3

В некоторых случаях у вас нет доступа к ObjectInputStream, и вы не можете переопределить readClassDescriptor(). Например, другая подсистема сериализует и десериализует эти объекты. В нашем случае у нас были устаревшие экземпляры, сериализованные в картах данных задания Quartz.

В этом случае вам необходимо сохранить неглубокое определение старого класса. Вы можете реализовать старые методы класса readResolve() и writeReplace().

class OldClass{
  private int aField;
  private Object readResolve() throws ObjectStreamException {
     return new NewClass(aField);
  }
  private Object writeReplace() throws ObjectStreamException {
     return new NewClass(aField);
  }
}

Ответ 4

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