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