Перемены классов, такие как добавление или удаление полей, поддерживают совместимость с последовательной последовательностью?
У меня есть вопрос о сериализации Java в сценариях, где вам может потребоваться изменить ваш сериализуемый класс и поддерживать обратную совместимость.
Я прихожу из глубокого опыта С#, поэтому, пожалуйста, позвольте мне сравнить Java с .NET.
В моем сценарии Java мне нужно сериализовать объект с механизмом сериализации Java runtime и сохранить двоичные данные в постоянном хранилище для повторного использования объектов в будущем. Проблема в том, что в будущем классы могут подвергаться изменениям. Поля могут быть добавлены или удалены.
Я не знаю сериализацию Java в глубине, за исключением этой фантастической статьи о том, как не программировать в Java при работе с сериализацией,
Как я полагаю (d), serialVersionUID играет ключевую роль в сериализации Java, и именно здесь мне нужна ваша помощь.
Помимо примера статьи (я знаю, что это плохое кодирование), это поле не будет изменено, когда Eclipse попросит его обновить после изменения класса?
Я помню из мира .NET, что при добавлении новых полей я должен добавить атрибут [OptionalField]
в поле, чтобы получить обратную совместимость, поэтому CLR не потребует его в старых сериализованных данных. Кроме того, когда мне нужно отказаться от поля, я должен удалить только общедоступные методы, а не частные поля.
Каковы рекомендации по лучшей сериализации?
Спасибо.
[Добавить] Вот пример. Предположим, что у меня есть класс Foo
public class Foo {
private String bar;
}
Затем я перехожу к:
public class Foo {
private String bar;
private Integer eggs;
}
Совместимость между этими двумя версиями? Если я десериализую "oldFoo", когда у меня скомпилирован "newFoo", яйца равны нулю или вызывается исключение? Я предпочитаю первый, очевидно!!
Ответы
Ответ 1
Скажем, у вас есть класс MyClass
, и вы хотите обеспечить совместимость с сериализацией в будущем или, по крайней мере, убедитесь, что вы не меняете свою сериализованную форму непреднамеренно. В большинстве случаев вы можете использовать Verify.assertSerializedForm()
из Утилиты тестирования коллекций GS.
Начните с написания теста, который утверждает, что ваш класс имеет serialVersionUID
из 0L
и имеет последовательную форму, содержащую пустую строку.
@Test
public void serialized_form()
{
Verify.assertSerializedForm(
0L,
"",
new MyClass());
}
Запустите тест. Он будет терпеть неудачу, поскольку String представляет кодировку Base64 и никогда не будет пустой.
org.junit.ComparisonFailure: Serialization was broken. <Click to see difference>
Когда вы нажмете, чтобы увидеть разницу, вы увидите фактическую кодировку Base64. Вставьте его в пустую строку.
@Test
public void serialized_form()
{
Verify.assertSerializedForm(
0L,
"rO0ABXNyAC9jYXJhbWVsa2F0YS5zaHVrbmlfZ29lbHZhLkV4ZXJjaXNlOVRlc3QkTXlDbGFzc56U\n"
+ "hVp0q+1aAgAAeHA=",
new MyClass());
}
Повторите тест. Вероятно, он снова сработает с сообщением об ошибке вроде этого.
java.lang.AssertionError: serialVersionUID differ expected:<0> but was:<-7019839295612785318>
Вставьте новый serialVersionUID в тест вместо 0L.
@Test
public void serialized_form()
{
Verify.assertSerializedForm(
-7019839295612785318L,
"rO0ABXNyAC9jYXJhbWVsa2F0YS5zaHVrbmlfZ29lbHZhLkV4ZXJjaXNlOVRlc3QkTXlDbGFzc56U\n"
+ "hVp0q+1aAgAAeHA=",
new MyClass());
}
Тест будет проходить до тех пор, пока вы не измените сериализованную форму. Если вы случайно нарушите тест (измените сериализованную форму), первое, что нужно сделать, это проверить, что вы указали serialVerionUID
в классе Serializable. Если вы оставите это, JVM генерирует его для вас, и он довольно хрупкий.
public class MyClass implements Serializable
{
private static final long serialVersionUID = -7019839295612785318L;
}
Если тест все еще сломан, вы можете попытаться восстановить сериализованную форму, пометив новые поля как переходные, полностью контролируя сериализованную форму с помощью writeObject() и т.д.
Если тест все еще сломан, вам нужно решить, следует ли находить и возвращать свои изменения, которые разбивали сериализацию или рассматривали ваши изменения как намеренное изменение в сериализованной форме.
Когда вы меняете сериализованную форму специально, вам нужно обновить строку Base64, чтобы пройти тест. Когда вы это сделаете, это важно, что вы меняете serialVersionUID
одновременно. Неважно, какой номер вы выберете, пока это номер, который вы никогда раньше не использовали для класса. Соглашение состоит в том, чтобы изменить его на 2L
, затем 3L
и т.д. Если вы начинаете с случайно генерируемого serialVersionUID
(например, -7019839295612785318L
в примере), вы все равно должны указывать номер до 2L
потому что он все еще является второй версией сериализованной формы.
Примечание. Я разработчик коллекций GS.
Ответ 2
Лучше не использовать сериализацию, когда вам нужно хранить свои данные в течение длительного периода времени. Попробуйте использовать базу данных или протокол протокола (протокол Буферы - это способ кодирования структурированных данных в эффективном, но расширяемом формате).
Ответ 3
Поддержка встроенной сериализации Java в основном полезна для краткосрочного хранения или передачи по сети, поэтому экземпляры приложения могут взаимодействовать с минимальными усилиями. Если вы используете более долгосрочное хранилище, я бы посоветовал вам взглянуть на некоторые методы сериализации XML, такие как JAXB.
Ответ 4
Если вы хотите управлять сериализованной версией класса, вы должны реализовать интерфейс Externalizable и указать, как сериализовать и десериализовать состояние вашего класса. Таким образом, сериализованное состояние может быть проще, чем "реальное" состояние. Например, объект TreeMap имеет состояние, которое является красно-черным деревом, а сериализованная версия - это всего лишь список значений ключа (и дерево воссоздается, когда объект десериализуется).
Однако, если ваш класс прост и у него есть только необязательные поля, вы можете использовать ключевое слово "переходный" и сделать сериализацию по умолчанию игнорировать его. Например:
public class Foo {
private String bar;
private transient Integer eggs;
}
Ответ 5
К сожалению, у меня нет глубоких знаний о С#, но на основе ваших слов я могу заключить, что сериализация Java слабее. Поле serialVersionUID является необязательным и может помочь, только если вы изменили двоичную подпись класса, но не изменили сериализуемые поля. Если вы изменили поля, вы не сможете прочитать ранее сериализованный объект.
Единственным обходным решением является внедрение собственного механизма поиска. Java позволяет это. Вы должны реализовать свои собственные методы readObject()
и writeObject()
. Эти методы должны быть достаточно умными, чтобы поддерживать обратную совместимость.
Подробнее см. javadoc java.io.Serializable
.
Ответ 6
Если вы установите serialVersionUID в константу (пусть говорят 1), вы можете свободно добавлять новые поля, не нарушая ничего. Если оставить serialVersionUID таким же образом между версиями, вы сообщаете алгоритму сериализации, что вы знаете, что классы совместимы.