Когда сериализация использования объектов Java, чтобы сохранить состояние в файлах, или как блобы в базах данных, потенциал возникает, что версия класса, читая данные отличается чем версия, которая записала данные.
Управление версиями повышает некоторые фундаментальные вопросы об идентификационных данных класса, включая то, что составляет совместимое изменение. Совместимое изменение является изменением, которое не влияет на контракт между классом и его вызывающими сторонами.
Этот раздел описывает цели, предположения, и решение, которое пытается рассмотреть эту проблему, ограничивая виды позволенных изменений и тщательно выбирая механизмы.
Предложенное решение обеспечивает механизм для "автоматической" обработки классов, которые развиваются, добавляя поля и добавляя классы. Сериализация обработает управление версиями без специфичных для класса методов, которые будут реализованы для каждой версии. Потоковый формат может быть пересечен, не вызывая специфичные для класса методы.
Поддерживайте двунаправленную передачу между различными версиями класса, работающего в различных виртуальных машинах:
Определение механизма, который позволяет потоки классов чтения Java, записанные более старыми версиями того же самого класса.
Определение механизма, который позволяет потоки классов записи Java, предназначенные, чтобы быть считанным более старыми версиями того же самого класса.
Обеспечьте сериализацию по умолчанию для персистентности и для RMI.
Выполните хорошо и произведите компактные потоки в простых случаях, так, чтобы RMI мог использовать сериализацию.
Будьте в состоянии идентифицировать и загрузить классы, которые соответствуют точный класс, используемый, чтобы записать поток.
Сохраните издержки низкими для неимеющих версию классов.
Используйте потоковый формат, который позволяет обход потока, не имея необходимость вызывать методы, определенные для объектов, сохраненных в потоке.
Управление версиями только применится к сериализуемым классам, так как оно должно управлять потоковым форматом, чтобы достигнуть этого цели. Классы Externalizable будут ответственны за свое собственное управление версиями, которое связывается к внешнему формату.
Все данные и объекты должны быть считаны из, или пропущены в, поток в том же самом порядке, как они были записаны.
Классы развиваются индивидуально так же как совместно с супертипами и подтипами.
Классы идентифицируются по имени. Два класса с тем же самым именем могут быть различными версиями или абсолютно различными классами, которые можно отличить только, сравнивая их интерфейсы или сравнивая хеши интерфейсов.
Сериализация по умолчанию не будет выполнять преобразований типов.
Потоковый формат только должен поддерживать линейную последовательность изменений типа, не произвольное ветвление типа.
В развитии классов это - ответственность развитого (позже версия) класс, чтобы поддержать контракт, установленный неразвитым классом. Это принимает две формы. Во-первых, развитый класс не должен повредить существующие предположения об интерфейсе, обеспеченном оригинальной версией, так, чтобы развитый класс мог использоваться вместо оригинала. Во-вторых, связываясь с оригиналом (или предыдущий) версии, развитый класс должен предоставить достаточную и эквивалентную информацию, чтобы позволить более ранней версии продолжать удовлетворять неразвитый контракт.
В целях обсуждения здесь, каждый класс реализует и расширяет интерфейс или контракт, определенный его супертипом. Новые версии класса, например foo', должен продолжать удовлетворять контракт для foo и может расширить интерфейс или изменить его реализацию.
Передача между объектами через сериализацию не является частью контракта, определенного этими интерфейсами. Сериализация является частным протоколом между реализациями. Это - обязанность реализаций связаться достаточно, чтобы позволить каждой реализации продолжать удовлетворять контракт, ожидаемый его клиентами.
Спецификация языка Java обсуждает совместимость на уровне двоичных кодов классов Java, поскольку те классы развиваются. Большая часть гибкости совместимости на уровне двоичных кодов прибывает из использования позднего связывания символьных ссылок для имен классов, интерфейсов, полей, методов, и так далее.
Следующее является принципиальными аспектами проекта для управления версиями сериализированных объектных потоков.
Механизм сериализации по умолчанию будет использовать символьную модель для того, чтобы связать поля в потоке к полям в соответствующем классе в виртуальной машине.
Каждый класс, на который ссылаются в потоке, однозначно определит себя, его супертип, и типы и имена каждого сериализуемого поля, записанного потоку. Поля упорядочиваются с типами примитивов, сначала сортированными именем поля, сопровождаемым объектными полями, сортированными именем поля.
Два типа данных могут произойти в потоке для каждого класса: необходимые данные (соответствующий непосредственно к сериализуемым полям объекта); и дополнительные данные (состоящий из произвольной последовательности примитивов и объектов). Потоковый формат определяет, как необходимые и дополнительные данные происходят в потоке так, чтобы целый класс, необходимое, или дополнительные части могли быть пропущены в случае необходимости.
Необходимые данные состоят из полей объекта в порядке, определенном дескриптором класса.
Дополнительные данные пишутся потоку и не соответствуют непосредственно полям класса. Сам класс ответственен за длину, типы, и управление версиями этой дополнительной информации.
Если определено для класса, writeObject/readObject методы заменяют механизм по умолчанию к записи-чтению состояние класса. Эти методы пишут и читают дополнительные данные для класса. Необходимые данные пишутся, вызывая defaultWriteObject и читайте, вызывая defaultReadObject.
Потоковый формат каждого класса идентифицируется при помощи Потокового Уникального идентификатора (SUID). По умолчанию это - хеш класса. Все более поздние версии класса должны объявить Потоковый Уникальный идентификатор (SUID), с которым они являются совместимыми. Это принимает меры против классов с тем же самым именем, которое могло бы непреднамеренно быть идентифицировано как являющийся версиями единого класса.
Подтипы ObjectOutputStream и ObjectInputStream может включать их собственную информацию, идентифицирующую класс, используя annotateClass метод; например, MarshalOutputStream встраивает URL класса.
С этими понятиями мы можем теперь описать, как проект справится с различными случаями развивающегося класса. Случаи описываются с точки зрения потока, записанного некоторой версией класса. Когда поток читается назад той же самой версией класса, нет никакой потери информации или функциональности. Поток является единственным источником информации об исходном классе. Его описания класса, в то время как подмножество исходного описания класса, достаточны, чтобы подойти данные в потоке с версией воссоздаваемого класса.
Описания с точки зрения потока, считанного, чтобы воссоздать или более раннюю или более позднюю версию класса. В языке систем RPC это - "получатель, делает правильную" систему. Писатель пишет его данные в самой подходящей форме, и получатель должен интерпретировать ту информацию, чтобы извлечь части, в которых это нуждается и заполнить части, которые не доступны.
5.6.1 Несовместимые Изменения
Несовместимые изменения к классам являются теми изменениями, для которых не может сохраняться гарантия функциональной совместимости. Несовместимые изменения, которые могут произойти, развивая класс:
Удаляя поля - Если поле удаляется в классе, записанный поток не будет содержать свое значение. Когда поток будет считан более ранним классом, значение поля будет установлено в значение по умолчанию, потому что никакое значение не доступно в потоке. Однако, это значение по умолчанию может неблагоприятно повредить возможность более ранней версии выполнить ее контракт.
Перемещая вверх классы или вниз иерархию - Это не может быть позволено, так как данные в потоке появляются в неправильной последовательности.
Изменяя нестатическое поле на статический или непереходное поле переходному процессу - полагаясь на сериализацию по умолчанию, это изменение эквивалентно удалению поля от класса. Эта версия класса не будет писать, что данные к потоку, таким образом, это не будет доступно, чтобы быть считанным более ранними версиями класса. Удаляя поле, поле более ранней версии будет инициализировано к значению по умолчанию, которое может заставить класс перестать работать неожиданными способами.
Изменяя объявленный тип примитивного поля - Каждая версия класса пишет данные со своим объявленным типом. Более ранние версии класса, пытающегося считать поле, перестанут работать, потому что тип данных в потоке не соответствует тип поля.
Изменение writeObject или readObject метод так, чтобы это больше не записало или считало полевые данные по умолчанию или изменение этого так, чтобы это попыталось записать это или считать это, когда предыдущая версия не сделала. Полевые данные по умолчанию должны последовательно или появляться или не появляться в потоке.
Изменение класса от Serializable к Externalizable или наоборот несовместимое изменение, так как поток будет содержать данные, которые являются несовместимыми с реализацией доступного класса.
Изменение класса от неперечислимого типа до перечислимого типа или наоборот так как поток будет содержать данные, которые являются несовместимыми с реализацией доступного класса.
Удаление также Serializable или Externalizable несовместимое изменение с тех пор когда записано, оно больше не будет предоставлять поля, необходимые более старым версиям класса.
Добавление writeReplace или readResolve метод к классу является несовместимым, если поведение произвело бы объект, который является несовместимым с любой более старой версией класса.
5.6.2 Совместимые Изменения
Совместимые изменения к классу обрабатываются следующим образом:
Добавляя поля - Когда у воссоздаваемого класса есть поле, которое не происходит в потоке, то поле в объекте будет инициализировано к значению по умолчанию для его типа. Если специфичная для класса инициализация необходима, класс может обеспечить readObject метод, который может инициализировать поле к значениям не по умолчанию.
Добавляя классы - поток будет содержать иерархию типа каждого объекта в потоке. Сравнение этой иерархии в потоке с текущим классом может обнаружить дополнительные классы. С тех пор нет никакой информации в потоке, от которого можно инициализировать объект, поля класса будут инициализированы к значениям по умолчанию.
Удаляя классы - Сравнение иерархии классов в потоке с тем из текущего класса может обнаружить, что класс был удален. В этом случае поля и объекты, соответствующие тому классу, читаются из потока. Примитивные поля отбрасываются, но объекты, на которые ссылается удаленный класс, создаются, так как они могут быть упомянуты позже в потоке. Они будут собраны "мусор", когда поток будет собран "мусор" или сбрасывается.
Добавление writeObject/readObject методы - Если у версии, читая поток есть эти методы тогда readObject как ожидают, как обычно, считает необходимые данные, записанные потоку сериализацией по умолчанию. Это должно вызвать defaultReadObject сначала прежде, чем считать любые дополнительные данные. writeObject метод, как ожидают, как обычно вызовет defaultWriteObject записать необходимые данные и затем может записать дополнительные данные.
Удаление writeObject/readObject методы - Если у класса, читая поток нет этих методов, необходимые данные, будут считаны сериализацией по умолчанию, и дополнительные данные будут отброшены.
Добавление java.io.Serializable - Это эквивалентно добавлению типов. Не будет никаких значений в потоке для этого класса, таким образом, его поля будут инициализированы к значениям по умолчанию. Поддержка разделения на подклассы несериализуемых классов требует, чтобы у супертипа класса был конструктор без аргументов, и сам класс будет инициализирован к значениям по умолчанию. Если конструктор без аргументов не доступен, InvalidClassException бросается.
Изменяя доступ к полю - общественность модификаторов доступа, пакет, защищенный, и частный, не имеет никакого эффекта на возможность сериализации присвоить значения полям.
Изменяя поле от статического до нестатического или переходного непереходному процессу - полагаясь на сериализацию по умолчанию, чтобы вычислить сериализуемые поля, это изменение эквивалентно добавлению поля к классу. Новое поле будет записано потоку, но более ранние классы проигнорируют значение, так как сериализация не будет присваивать значения статическим или переходным полям.