Как работать с 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
Ввернувшись, отскакивайте старые серверы, если вы счастливы продвигать свои изменения. Любой взлом для достижения вашей цели просто добавит больше кода дерьма и сделает больше беспорядка и головных болей.