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

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

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

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

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


person Alejandro    schedule 14.03.2011    source источник


Ответы (4)


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

На самом деле это говорит о том, что вы создаете подкласс 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) приемлемым для всех заинтересованных сторон.

person Stephen C    schedule 14.03.2011
comment
Спасибо! Кажется, это касается только случая, когда устаревший класс необходимо десериализовать в новый класс, но не сериализовать обратно в совместимый поток, который могли бы понять серверы со старым кодом. Возможно, применим тот же принцип, но мне нужно исследовать это дальше. Еще раз спасибо за ссылку! :) - person Alejandro; 15.03.2011
comment
Возможно, вам также придется добавить логику для обработки массивов измененного класса. Имя класса выглядит примерно так: [Lpackage.class.name - person thomas88wp; 25.12.2014
comment
Я бы также добавил, что как только вы это сделаете, вы больше не сможете добавлять/удалять поля из класса, не вызывая ошибок при десериализации. Я не уверен, почему это происходит, но вместо того, чтобы игнорировать удаленные поля, например, он пытается использовать следующее доступное поле, вызывая ошибку ClassCastException. - person thomas88wp; 26.12.2014
comment
Да. Это решение также подходит, когда вы хотите изменить имя класса, в который хотите выполнить десериализацию. Допустим, вы сериализовали с классом А, но хотите десериализовать в класс Б, вы можете это сделать. - person ACV; 18.10.2017

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

person Ted Hopp    schedule 14.03.2011
comment
+1 - JSON может быть другой альтернативой. Утиная печать — ваш друг. - person duffymo; 15.03.2011
comment
@duffymo — XStream поддерживает вывод в JSON, а также в XML. - person Ted Hopp; 15.03.2011
comment
Да, со временем я так и сделаю, но пока мне нужно более быстрое решение без добавления нового механизма сериализации XML/JSON. Спасибо, в любом случае. - person Alejandro; 15.03.2011
comment
мы используем здесь xstream и сериализуем в java перед сохранением объектов в базах данных. Работает отлично. Вы можете легко изменить сохраненный объект xml или настроить xstream для псевдонимов таких вещей, как классы/поля/пакеты во время выполнения. Хотя вам, вероятно, следует запекать такие миграции непосредственно в данных. - person DragonFax; 17.03.2011

В некоторых случаях у вас нет доступа к 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);
  }
}
person hughw    schedule 27.10.2011

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

person mP.    schedule 15.03.2011