При использовании сериализации в класс рекомендуют добавить такое поле:
private static final long serialVersionUID = 27507467L;В этом посте разберёмся, зачем это нужно, когда прописывать serialVersionUID и когда менять. В конце поговорим про недостатки сериализации в java.
Итак, в процессе сериализации 2 участника: отправитель и получатель. У каждого из них есть код класса Х. Отправитель сериализует экземпляр Х и отправляет по сети.
Из прошлого поста вы знаете, что в этом массиве байтов есть serialVersionUID. Получатель читает имя класса и первым делом сравнивает serialVersionUID из сообщения с serialVersionUID своего класса.
▫️ Если совпадают - начинается десериализация
▫️ Если нет - выбрасывается
InvalidClassException
Когда serialVersionUID не указан в классе явно, JVM вычисляет его в рантайме на основе имени класса, интерфейсов, полей и методов. Добавили новый метод - serialVersionUID изменился. Поэтому рекомендуется зафиксировать serialVersionUID, даже если поля класса не меняются.Другой вариант - когда класс эволюционирует и передаёт другой набор данных. Сервисы не всегда обновляются одновременно, поэтому в переходный период возникают две проблемы:
🔸 Как новому коду читать данные, созданные старым кодом? (backward compatibility)
🔸 Как старому коду читать данные, созданные новым кодом? (forward compatibility)
Приходится мириться с наличием старых версий и писать код соответственно:
1️⃣ Задать в классе serialVersionUID. Никогда не менять
2️⃣ Добавить методы readObject и writeObject и прописать порядок записи и чтения полей
3️⃣ Писать тесты на совместимость версий
Набор изменений при этом весьма ограничен:
✅ Можно добавлять новые поля в конец байтового стрима
✅ Можно менять видимость полей и методов
❌ Нельзя удалять поля
❌ Нельзя менять тип полей
Пара "имя класса-serialVersionUID" работает как фильтр - можно десериализовать набор байтов или нет. Когда serialVersionUID не задан в классе, он генерируется JVM. Для прямой и обратной совместимости serialVersionUID может быть любым, но постоянным. Методы writeObject и readObject задают чёткий порядок чтения/записи, но сильно разгуляться не получится.
Уже отсюда понятно, что на практике с сериализацией море проблем:
▪️ Разработка усложняется: всегда нужно иметь в виду forward/backward совместимость, набор доступных изменений сильно ограничен.
▪️ Нарушается инкапсуляция, так как private поля передаются по сети.
▪️ Ограниченные сценарии использования. Получатель и отправитель должны быть на java.
▪️ Небезопасно. Десериализация - сладкий пирожок для разных типов атак. В 2016 их было так много, что тот год на конференциях называли Java deserialization apocalypse year. В 2021 году уязвимости на основе сериализации встречаются даже в Intellij IDEA и Kubernetes.
Что с этим делать и как сериализация выглядит на практике - поговорим в третьей части.