Где должны быть файлы классов при десериализации объектов java

Обновлено: 06.07.2024

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

Мы будем использовать ниже Employee объект класса в качестве примера для объяснения

Что такое сериализация и десериализация?

В Java мы создаем несколько объектов, которые живут и умирают соответственно, и каждый объект обязательно умрет, когда JVM умрет. Но иногда нам может потребоваться повторно использовать объект между несколькими JVM или мы можем перенести объект на другой компьютер по сети.

Проще говоря, сериализация объекта — это процесс сохранения состояния объекта в последовательности байтов, а десериализация — процесс восстановления объекта из этих байтов. Как правило, полный процесс называется сериализацией, но я думаю, что для большей ясности лучше классифицировать их как отдельные:


Процесс сериализации не зависит от платформы, объект, сериализованный на одной платформе, может быть десериализован на другой платформе.

Для сериализации и десериализации, наш объект в файл , мы должны вызов ObjectOutputStream.writeObject() и , ObjectInputStream.readObject() как это сделано в следующем коде:

Только классы, которые реализуют Serializable, могут быть сериализированы

Подобно интерфейсу Cloneable для клонирования Java в сериализации, у нас есть один интерфейс маркера, Serializable, который работает как флаг для JVM. Любой класс, который реализует Serializable интерфейс напрямую или через своего родителя, может быть сериализован, а классы, которые не реализуют, Serializable не могут быть сериализованы.

Процесс сериализации Java по умолчанию полностью рекурсивен, поэтому всякий раз , когда мы пытаемся сериализовать один объект, то попытка процесса сериализации сериализовать все поля (примитивные и ссылочные) с нашим классом ( за исключением static и transient полей).


Когда класс реализует
Serializable интерфейс, все его подклассы также сериализуются. Но когда объект имеет ссылку на другой объект, эти объекты должны реализовывать
Serializable интерфейс отдельно. Если у нашего класса есть хотя бы одна ссылка на не
Serializable класс, JVM сгенерирует
NotSerializableException .

Почему сериализуемый объект не реализуется объектом?

Теперь возникает вопрос: если Serialization является очень базовой функциональностью, и любой класс, который не реализует, Serializable не может быть сериализован, то почему Serializable не реализуется сам по Object себе? Таким образом, все наши объекты могут быть сериализованы по умолчанию.

Object Класс не реализует Serializable интерфейс , потому что мы не можем сериализовать все объекты, например сериализацию нити не имеет никакого смысла , так как нить работает в моей JVM будет использовать память моей системы, сохраняющийся его и пытаемся запустить его в JVM будет не имеет смысла.

Переходные и статические поля не сериализуются

Если мы хотим сериализовать один объект, но не хотим сериализовать определенные поля, мы можем пометить эти поля как временные .

Все статические поля принадлежат классу, а не объекту, и процесс сериализации сериализует объект, поэтому статические поля не могут быть сериализованы.

  1. Сериализация не заботится о модификаторах доступа области, таких как private . Все непереходные и нестатические поля считаются частью постоянного состояния объекта и могут быть сериализованы.
  2. Мы можем присваивать значения конечным полям только в обработчиках, и процесс сериализации не вызывает никакого конструктора, но все же может присваивать значения конечным полям.

Что такое serialVersionUID? И почему мы должны это объявлять?

Предположим, у нас есть класс, и мы сериализовали его объект в файл на диске, и из-за некоторых новых требований мы добавили / удалили одно поле из нашего класса. Теперь, если мы попытаемся десериализовать уже сериализованный объект, мы получим InvalidClassException ; Почему?

Мы получаем его, потому что по умолчанию JVM связывает номер версии с каждым сериализуемым классом для управления версиями класса. Он используется для проверки того, что сериализованные и десериализованные объекты имеют одинаковые атрибуты и, следовательно, совместимы с десериализацией. Номер версии сохраняется в поле с именем serialVersionUID . Если сериализуемый класс не объявляет a serialVersionUID , JVM сгенерирует его автоматически во время выполнения.

Если мы изменим структуру нашего класса, например поля удаления / добавления, этот номер версии также изменится, и в соответствии с JVM наш класс не будет совместим с версией класса сериализованного объекта. Вот почему мы получаем исключение, но если вы действительно думаете об этом, почему оно должно быть выброшено только потому, что я добавил поле? Не может ли поле просто установить его значение по умолчанию, а затем записать в следующий раз?

Да, это можно сделать, предоставив serialVersionUID поле вручную и убедившись, что оно всегда одинаково. Настоятельно рекомендуется, чтобы каждый сериализуемый класс объявлял его serialVersionUID как сгенерированный класс зависимым от компилятора и, следовательно, может привести к непредвиденным последствиям . InvalidClassExceptions


Вы можете использовать утилиту, поставляемую с дистрибутивом JDK,
serialver для просмотра того, каким будет этот код по умолчанию (это просто хэш-код объекта по умолчанию).

Настройка сериализации и десериализации с помощью методов writeObject и readObject

JVM полностью контролирует сериализацию объекта в процессе сериализации по умолчанию, но есть много недостатков в использовании процесса сериализации по умолчанию, некоторые из которых:

  1. Он не может обработать сериализацию полей, которые не сериализуются.
  2. Процесс десериализации не вызывает конструкторов при создании объекта, поэтому он не может вызвать логику инициализации, предоставленную конструктором.

Но мы можем переопределить это поведение сериализации по умолчанию внутри нашего Java-класса и предоставить некоторую дополнительную логику для улучшения нормального процесса. Это можно сделать, предоставив два метода writeObject и readObject внутри класса, который мы хотим сериализовать:

Объявление обоих методов как закрытых необходимо (общедоступные методы не будут работать), поэтому, кроме JVM, ничто другое не сможет их увидеть. Это также доказывает, что ни один метод не наследуется, не переопределяется и не перегружается. JVM автоматически проверяет эти методы и вызывает их в процессе сериализации-десериализации. JVM может вызывать эти закрытые методы, но другие объекты не могут. Таким образом, целостность класса сохраняется, и протокол сериализации может продолжать работать в обычном режиме.

Несмотря на то, что эти специализированные частные методы предоставляются, сериализация объектов работает так же, вызывая ObjectOutputStream.writeObject() или ObjectInputStream.readObject() .

Вызов ObjectOutputStream.writeObject() или ObjectInputStream.readObject() запускает протокол сериализации. Сначала проверяется объект на предмет его реализации Serializable , а затем проверяется, предоставляется ли какой-либо из этих закрытых методов. Если они предоставляются, класс потока передается в качестве параметра этим методам, предоставляя коду контроль над его использованием.

Мы можем вызывать ObjectOutputStream.defaultWriteObject() и ObjectInputStream.defaultReadObject() из этих методов получить логику сериализации по умолчанию. Эти вызовы делают то, на что они похожи — они выполняют запись и чтение сериализованного объекта по умолчанию, что важно, потому что мы не заменяем обычный процесс; мы только добавляем к этому.

Эти закрытые методы могут использоваться для любой настройки, которую вы хотите выполнить в процессе сериализации, например, шифрование может быть добавлено к выводу и дешифрование ко входу (обратите внимание, что байты записываются и читаются в виде открытого текста без всякой обфускации). Они могут быть использованы для добавления дополнительных данных в поток, возможно, код версии компании, возможности действительно безграничны.

Остановка сериализации и десериализации

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

Это означает, что любой может сериализовать и десериализовать объект нашего класса. Но что, если мы не хотим, чтобы наш класс был сериализован или десериализован? Например, какой у нас класс одноэлементный и мы хотим предотвратить создание любого нового объекта? Помните, что процесс десериализации создает новый объект .

Чтобы остановить сериализацию для нашего класса, мы можем, опять же, использовать вышеупомянутые приватные методы, чтобы просто бросить NotSerializableException . Любая попытка сериализации или десериализации нашего объекта теперь всегда приводит к возникновению исключения. И поскольку эти методы объявлены как private , никто не может переопределить ваши методы и изменить их.

Однако это является нарушением принципа подстановки Лискова. И, writeReplace и readResolve методы могут быть использованы для достижения синглтоноподобного поведения. Эти методы используются, чтобы позволить объекту предоставить альтернативное представление для себя в ObjectStream . Проще говоря, readResolve может использоваться для изменения данных, десериализованных посредством readObject метода, и writeReplace может использоваться для изменения данных, которые сериализуются через writeObject .

Сериализация Java также может использоваться для глубокого клонирования объекта . Клонирование Java — самая дискуссионная тема в сообществе Java, и оно, безусловно, имеет свои недостатки, но это все еще самый популярный и простой способ создания копии объекта до тех пор, пока этот объект не заполнит обязательные условия клонирования Java. Я покрыл клонирование в деталях в 3 статье длиной Java Клонирование серии , которая включает в себя статью , такой как Java Клонирование и типы Cloning (поверхностная и глубокая) подробно на примере , Java Клонирование — Конструктор копирования Versus Cloning , Java Клонирование — Даже копировать конструктор не Достаточно , прочитайте их, если хотите узнать больше о клонировании.

Заключение

  1. Сериализация — это процесс сохранения состояния объекта в последовательность байтов, которая затем может быть сохранена в файле или отправлена ​​по сети, а десериализация — это процесс восстановления объекта из этих байтов.
  2. Только подклассы Serializable интерфейса могут быть сериализованы.
  3. Если наш класс не реализует Serializable интерфейс или если у него есть ссылка на не- Serializable класс, JVM сгенерирует NotSerializableException .
  4. Все transient и static поля не сериализуются.
  5. serialVersionUID Используются для проверки того, что сериализации и десериализации объекты имеют те же атрибуты , и , таким образом, совместимы с десериализацией.
  6. Мы должны создать serialVersionUID поле в нашем классе, поэтому, если мы изменим структуру нашего класса (добавление / удаление полей), JVM не будет проходить InvalidClassException . Если мы не предоставляем его, JVM предоставляет тот, который может измениться при изменении структуры нашего класса.
  7. Мы можем переопределить поведение сериализации по умолчанию внутри нашего Java-класса, предоставив реализацию writeObject и readObject методы.
  8. И мы можем назвать ObjectOutputStream.defaultWriteObject() и ObjectInputStream.defaultReadObject от writeObject и readObject методы получения сериализации по умолчанию и десериализации логике.
  9. Мы можем выбросить NotSerializableException исключение из writeObject и readObject , если мы не хотим, чтобы наш класс был сериализован или десериализован.

Процесс сериализации Java может быть дополнительно настроен и улучшен с помощью Externalizable интерфейса, который я объяснил в разделе Как настроить сериализацию в Java с помощью интерфейса Externalizable .

Вы можете найти полный исходный код для этой статьи в этом репозитории GitHub , и, пожалуйста, не стесняйтесь оставить свой ценный отзыв.

В сегодняшнем мире типичное промышленное приложение будет иметь множество компонентов и будет распространено через различные системы и сети. В Java всё представлено в виде объектов; Если двум компонентам Java необходимо общаться друг с другом, то им необходим механизм для обмена данными. Есть несколько способов реализовать этот механизм. Первый способ это разработать собственный протокол и передать объект. Это означает, что получатель должен знать протокол, используемый отправителем для воссоздания объекта, что усложняет разработку сторонних компонентов. Следовательно, должен быть универсальный и эффективный протокол передачи объектов между компонентами. Сериализация создана для этого, и компоненты Java используют этот протокол для передачи объектов.


Рисунок 1 демонстрирует высоко-уровневое представление клиент-серверной коммуникации, где объект передаётся с клиента на сервер посредством сериализации.

Рисунок 1.

Как сериализовать объект?

Для начала следует убедиться, что класс сериализуемого объекта реализует интерфейс java.io.Serializable как показано в листинге 1.

class TestSerial implements Serializable public byte version = 100;
public byte count = 0;
>

* This source code was highlighted with Source Code Highlighter .

В Листинге 1 только одна вещь отличается от создания нормального класса, это реализация интерфейса java.io.Serializable . Интерфейс Serializable это интерфейс-маркер; в нём не задекларировано ни одного метода. Но говорит сериализующему механизму, что класс может быть сериализован.

Теперь у нас есть всё необходимое для сериализации объекта, следующим шагом будет фактическая сериализация объекта. Она делается вызовом метода writeObject() класса java.io.ObjectOutputStream , как показано в листинге 2.

public static void main( String args[]) throws IOException FileOutputStream fos = new FileOutputStream( "temp.out" );
ObjectOutputStream oos = new ObjectOutputStream(fos);
TestSerial ts = new TestSerial();
oos.writeObject(ts);
oos.flush();
oos.close();
>

* This source code was highlighted with Source Code Highlighter .

В листинге 2 показано сохранение состояния экземпляра TestSerial в файл с именем temp.out

Для воссоздания объекта из файла, необходимо применить код из листинга 3.

public static void main( String args[]) throws IOException FileInputStream fis = new FileInputStream( "temp.out" );
ObjectInputStream oin = new ObjectInputStream(fis);
TestSerial ts = (TestSerial) oin.readObject();
System. out .println( "version gray">* This source code was highlighted with Source Code Highlighter .

Восстановление объекта происходит с помощью вызова метода oin.readObject() . В методе происходит чтение набора байт из файла и создаие точной копии графа оригинального объекта. oin.readObject() может прочитать любой сериализованный объект, поэтому необходимо полученный объект приводить к конкретному типу.
Выполненный код выведет version=100 в стандартный вывод.

Формат сериализованного объекта

Как должен выглядеть сериализованный объект? Вспомните простой код из предыдущего раздела, который сериализует объект класса TestSerial и записывает в temp.out . В листинге 4 показано содержимое файла temp.out , в шестнадцатеричном виде.

AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65
73 74 A0 0C 34 00 FE B1 DD F9 02 00 02 42 00 05
63 6F 75 6E 74 42 00 07 76 65 72 73 69 6F 6E 78
70 00 64

Если вы снова посмотрите на TestSerial , то увидите, что у него всего 2 байтовых члена. Как показано в листинге 5.

public byte version = 100;
public byte count = 0;

* This source code was highlighted with Source Code Highlighter .

Размер байтовой переменной один байт, и следовательно полный размер объекта (без заголовка) — два байта. Но размер сериализованного объекта 51 байт. Удивлены? Откуда взялись эти дополнительные байты и что они обозначают? Они добавлены сериализующим алгоритмом и необходимы для воссоздания объекта. В следующем абзаце будет подробно описан этот алгоритм.

Алгоритм сериализации Java

К этому моменту у вас уже должно быть достаточно знаний, чтобы сериализовать объект. Но как работает этот механизм? Алгоритм сериализации делает следующие вещи:

  • запись метаданных о классе ассоциированном с объектом
  • рекурсивная запись описания суперклассов, до тех пор пока не будет достигнут java.lang.object
  • после окончания записи метаданных начинается запись фактических данных ассоциированных с экземпляром, только в этот раз начинается запись с самого верхнего суперкласса
  • рекурсивная запись данных ассоциированных с экземпляром начиная с самого низшего суперкласса

В листинге 6 указан пример охватывающий все возможные случаи сериализации

class parent implements Serializable int parentVersion = 10;
>

class contain implements Serializable int containVersion = 11;
>
public class SerialTest extends parent implements Serializable int version = 66;
contain con = new contain();

public int getVersion() return version;
>
public static void main( String args[]) throws IOException FileOutputStream fos = new FileOutputStream( "temp.out" );
ObjectOutputStream oos = new ObjectOutputStream(fos);
SerialTest st = new SerialTest();
oos.writeObject(st);
oos.flush();
oos.close();
>
>

* This source code was highlighted with Source Code Highlighter .

В примере сериализуется объект класса SerialTest , который наследуется от parent и содержит объект-контейнер класса contain . В листинге 7 показан сериализованный объект.

AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65
73 74 05 52 81 5A AC 66 02 F6 02 00 02 49 00 07
76 65 72 73 69 6F 6E 4C 00 03 63 6F 6E 74 00 09
4C 63 6F 6E 74 61 69 6E 3B 78 72 00 06 70 61 72
65 6E 74 0E DB D2 BD 85 EE 63 7A 02 00 01 49 00
0D 70 61 72 65 6E 74 56 65 72 73 69 6F 6E 78 70
00 00 00 0A 00 00 00 42 73 72 00 07 63 6F 6E 74
61 69 6E FC BB E6 0E FB CB 60 C7 02 00 01 49 00
0E 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E 78
70 00 00 00 0B

На рисунке 2 показан сценарий алгоритма сериализации.

Рисунок 2.

  • AC ED: STREAM_MAGIC . Говорит о том, что используется протокол сериазизации.
  • 00 05: STREAM_VERSION . Версия сериализации.
  • 0x73: TC_OBJECT . Обозначение нового объекта.
  • 0x72: TC_CLASSDESC . Обозначение нового класса.
  • 00 0A : Длина имени класса.
  • 53 65 72 69 61 6c 54 65 73 74: SerialTest , имя класса.
  • 05 52 81 5A AC 66 02 F6: SerialVersionUID , идентификатор класса.
  • 0x02 : Различные флаги. Этот специфический флаг говорит о том, что объект поддерживает сериализацию.
  • 00 02 : Число полей в классе.
  • 0x49 : Код типа поля. 49 это «I», которое закреплено за Int.
  • 00 07 : Длина имени поля.
  • 76 65 72 73 69 6F 6E: version , имя поля.
  • 0x74: TC_STRING . Обозначает новую строку.
  • 00 09 : Длина строки.
  • 4C 63 6F 6E 74 61 69 6E 3B: Lcontain; , Каноническое JVM обозначаение.
  • 0x78: TC_ENDBLOCKDATA , Конец опционального блока данных для объекта.
  • 0x72: TC_CLASSDESC . Обозначение нового класса.
  • 00 06 : Длина имени класса.
  • 70 61 72 65 6E 74: parent , имя класса
  • 0E DB D2 BD 85 EE 63 7A: SerialVersionUID , идентификатор класса.
  • 0x02 : Различные флаги. Этот флаг обозначает что класс поддерживает сериализацию.
  • 00 01 : Число полей в классе.
  • 0x49 : Код типа поля. 49 обозначает «I», которое закреплено за Int.
  • 00 0D : Длина имени поля.
  • 70 61 72 65 6E 74 56 65 72 73 69 6F 6E : parentVersion, имя поля.
  • 0x78: TC_ENDBLOCKDATA , конец опционального блока данных для объекта.
  • 0x70: TC_NULL , обозначает то что больше нет суперклассов, потому что мы достигли верха иерархии классов.
  • 00 00 00 0A: 10 , Значение parentVersion .
  • 00 00 00 42: 66 , Значение version .

* This source code was highlighted with Source Code Highlighter .

  • 0x73: TC_OBJECT , обозначает новый объект.
  • 0x72: TC_CLASSDESC , обозначает новый класс.
  • 00 07 : Длина имени класса.
  • 63 6F 6E 74 61 69 6E: contain , имя класса.
  • FC BB E6 0E FB CB 60 C7: SerialVersionUID , идентификатор этого класса.
  • 0x02 : Различные флаги. Этот флаг обозначает что класс поддерживает сериализацию.
  • 00 01 : Число полей в классе.
  • 0x49 : Код типа поля. 49 обозначает «I», которое закреплено за Int.
  • 00 0E : Длина имени поля.
  • 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion , имя поля.
  • 0x78: TC_ENDBLOCKDATA , конец опционального блока данных для объекта.
  • 0x70: TC_NULL
  • 00 00 00 0B: 11 , значение containVersion .

Заключение

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

Об авторе

Sathiskumar Palaniappan имеет более чем 4-х летний опыт работы в IT-индестрии, и работает с Java технологиями более 3 лет. На данный момент он работает system software engineer в Java Technology Center, IBM Labs. Также имеет опыт работы в телекоммуникационной индустрии.

Сериализация на Java. Пример сериализации Java. Пример сериализуемого интерфейса Java, пример десериализации, serialVersionUID, Внешний интерфейс.

Сериализация в Java была введена в JDK 1.1, и это одна из важных функций Core Java .

Сериализация на Java

  1. Сериализуемый в Java
  2. Рефакторинг классов с сериализацией и serialVersionUID
  3. Внешний интерфейс Java
  4. Методы сериализации Java
  5. Сериализация с наследованием
  6. Шаблон прокси-сервера Сериализации

Сериализуемый в Java

Если вы хотите, чтобы объект класса был сериализуемым, все, что вам нужно сделать, это реализовать интерфейс java.io.Serializable . Сериализуемый в java является интерфейсом маркера и не имеет полей или методов для реализации. Это похоже на процесс регистрации, с помощью которого мы делаем наши классы сериализуемыми.

Обратите внимание, что это простой java-компонент с некоторыми свойствами и методами получения и установки. Если вы хотите, чтобы свойство объекта не сериализовывалось для потоковой передачи, вы можете использовать ключевое слово transient , как я сделал с переменной зарплаты.

Теперь предположим, что мы хотим записать наши объекты в файл, а затем десериализовать их из того же файла. Поэтому нам нужны служебные методы, которые будут использовать ObjectInputStream и ObjectOutputStream для целей сериализации.

Обратите внимание, что аргументы метода работают с объектом, который является базовым классом любого объекта java. Это написано таким образом, чтобы быть общим по своей природе.

Теперь давайте напишем тестовую программу, чтобы увидеть сериализацию Java в действии.

Когда мы запускаем вышеуказанную тестовую программу для сериализации на java, мы получаем следующий вывод.

Поскольку зарплата является переходной переменной, ее значение не было сохранено в файл и, следовательно, не было извлечено в новом объекте. Аналогично статические значения переменных также не сериализуются, поскольку они принадлежат классу, а не объекту.

Рефакторинг классов с сериализацией и serialVersionUID

Сериализация в java допускает некоторые изменения в классе java, если их можно игнорировать. Некоторые изменения в классе, которые не повлияют на процесс десериализации, включают:

  • Добавление новых переменных в класс
  • Изменение переменных с переходных на непереходные, для сериализации это похоже на создание нового поля.
  • Изменение переменной со статической на нестатическую для сериализации похоже на создание нового поля.

Но для того, чтобы все эти изменения работали, класс java должен иметь serialVersionUID , определенный для класса. Давайте напишем тестовый класс только для десериализации уже сериализованного файла из предыдущего тестового класса.

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

Например, раскомментируйте только поле serialVersionUID из класса Сотрудник и запустите программу Тест сериализации . Теперь раскомментируйте поле пароля из класса Employee и запустите программу DeserializationTest , и вы увидите, что поток объектов успешно десериализован, поскольку изменение класса Employee совместимо с процессом сериализации.

Внешний интерфейс Java

Если вы заметили процесс сериализации java, это делается автоматически. Иногда мы хотим скрыть данные объекта, чтобы сохранить его целостность. Мы можем сделать это, реализовав интерфейс java.io.Externalizable и обеспечив реализацию методов write External() и readExternal () , которые будут использоваться в процессе сериализации.

Обратите внимание, что я изменил значения полей перед преобразованием их в поток, а затем во время чтения изменил изменения. Таким образом, мы можем поддерживать некоторую целостность данных. Мы можем создать исключение, если после считывания потоковых данных проверка целостности завершится неудачей. Давайте напишем тестовую программу, чтобы увидеть ее в действии.

Итак, какой из них лучше использовать для сериализации в java. На самом деле лучше использовать сериализуемый интерфейс, и к тому времени, когда мы дойдем до конца статьи, вы поймете, почему.

Методы сериализации Java

Если эти методы присутствуют в классе, они используются для целей сериализации.

  1. readObject(ois ObjectInputStream) : Если этот метод присутствует в классе, метод ObjectInputStream readObject() будет использовать этот метод для чтения объекта из потока.
  2. writeObject(oos ObjectOutputStream) : Если этот метод присутствует в классе, метод ObjectOutputStream writeObject() будет использовать этот метод для записи объекта в поток. Одним из распространенных способов использования является скрытие переменных объекта для поддержания целостности данных.
  3. Object writeReplace() : Если этот метод присутствует, то после процесса сериализации этот метод вызывается и возвращаемый объект сериализуется в поток.
  4. Объект readResolve() : Если этот метод присутствует, то после процесса десериализации этот метод вызывается для возврата конечного объекта вызывающей программе. Одним из способов использования этого метода является реализация одноэлементного шаблона с сериализованными классами. Подробнее читайте в Сериализация и синглтон .

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

Сериализация с наследованием

Иногда нам нужно расширить класс, который не реализует сериализуемый интерфейс. Если мы полагаемся на поведение автоматической сериализации и суперкласс имеет некоторое состояние, то они не будут преобразованы в поток и, следовательно, не будут извлечены позже.

Это одно место, где действительно помогают методы readObject() и writeObject (). Предоставляя их реализацию, мы можем сохранить состояние суперкласса в потоке, а затем получить его позже. Давайте посмотрим на это в действии.

СуперКласс-это простой компонент java, но он не реализует сериализуемый интерфейс.

Обратите внимание, что порядок записи и чтения дополнительных данных в поток должен быть одинаковым. Мы можем вложить некоторую логику в чтение и запись данных, чтобы сделать их безопасными.

Также обратите внимание, что класс реализует ObjectInputValidation интерфейс. Реализуя метод validateObject () , мы можем провести некоторые бизнес-проверки, чтобы убедиться, что целостность данных не нарушена.

Давайте напишем тестовый класс и посмотрим, сможем ли мы извлечь состояние суперкласса из сериализованных данных или нет.

Таким образом, таким образом, мы можем сериализовать состояние суперкласса, даже если он не реализует сериализуемый интерфейс. Эта стратегия удобна, когда суперкласс является сторонним классом, который мы не можем изменить.

Шаблон прокси-сервера Сериализации

Сериализация в java сопряжена с некоторыми серьезными подводными камнями, такими как;

  • Структура классов не может быть сильно изменена без нарушения процесса сериализации java. Поэтому, хотя в дальнейшем нам не понадобятся некоторые переменные, нам нужно сохранить их только для обратной совместимости.
  • Сериализация создает огромные риски для безопасности, злоумышленник может изменить последовательность потоков и нанести вред системе. Например, роль пользователя сериализуется, и злоумышленник изменяет значение потока, чтобы сделать его администратором и запустить вредоносный код.

Шаблон прокси-сервера сериализации Java-это способ повысить безопасность с помощью сериализации. В этом шаблоне внутренний закрытый статический класс используется в качестве прокси-класса для целей сериализации. Этот класс разработан таким образом, чтобы поддерживать состояние основного класса. Этот шаблон реализуется путем правильной реализации методов readResolve() и writeReplace () .

Давайте сначала напишем класс, который реализует шаблон прокси-сервера сериализации, а затем проанализируем его для лучшего понимания.

  • Как Данные , так и Прокси-сервер данных класс должны реализовывать сериализуемый интерфейс.
  • Прокси-сервер данных должен поддерживать состояние объекта данных.
  • Прокси-сервер данных является внутренним частным статическим классом, поэтому другие классы не могут получить к нему доступ.
  • Прокси-сервер данных должен иметь один конструктор, который принимает данные в качестве аргумента.
  • Данные класс должен предоставить writeReplace() метод, возвращающий Прокси-сервер данных экземпляр. Поэтому, когда объект данных сериализуется, возвращаемый поток относится к классу DataProxy. Однако класс DataProxy не виден снаружи, поэтому его нельзя использовать напрямую.
  • Прокси-сервер данных класс должен реализовать readResolve() метод, возвращающий Данные объект. Поэтому, когда класс данных десериализуется, внутренне десериализуется DataProxy, и когда вызывается метод readResolve (), мы получаем объект данных.
  • Наконец, реализуйте readObject() метод в классе данных и бросьте Исключение InvalidObjectException чтобы избежать атаки хакеров, пытающихся создать поток объектов данных и проанализировать его.

Давайте напишем небольшой тест, чтобы проверить, работает ли реализация или нет.

Когда мы запускаем выше класса, мы получаем результат ниже в консоли.

Если вы откроете файл data.set, вы увидите, что объект прокси-сервера данных сохранен в файле в виде потока.

Это все для сериализации в Java, это выглядит просто, но мы должны использовать это разумно, и всегда лучше не полагаться на реализацию по умолчанию. Скачайте проект по ссылке выше и поиграйте с ним, чтобы узнать больше.

Java предоставляет механизм, называемый сериализацией объектов, в котором объект может быть представлен в виде последовательности байтов, которая включает в себя данные об объекте, а также информацию о типе объекта и типах данных, хранящихся в объекте.

Содержание

После того, как сериализованный объект был записан в файл, он может быть прочтен из файла и десериализован, то есть информацию о типе и байты, которые представляют объект и его данные, можно использовать для воссоздания объекта в памяти.

Больше всего впечатляет то, что весь процесс независим от JVM (виртуальной машины Java), то есть объект может быть сериализован на одной платформе и десериализован на совершенно другой платформе.

Классы ObjectInputStream (поток входных данных объекта) и ObjectOutputStream (поток выходных данных объекта) - это высокоуровневые потоки, которые содержат методы для осуществления сериализации и десериализации объекта.

Класс ObjectOutputStream содержит много методов записи для осуществления записи различных типов данных, но среди них в особенности выделяется один метод:

Вышеуказанный метод осуществляет сериализацию Объекта и отправляет его в поток выходных данных. Аналогично, класс ObjectInputStream содержит следующий метод осуществления десериализации объекта:

Этим методом извлекается следующий Объект из потока данных и осуществляется его десериализация. Возвращенное значение - это Объект, поэтому необходимо привести его к соответствующему типу данных.

Чтобы продемонстрировать, каким образом сериализация работает в Java, я собираюсь использовать класс Employee (сотрудник), который обсуждался в начале книги. Предположим, что мы располагаем следующим классом Employee, который внедряет сериализируемый интерфейс.

Пример

Обратите внимание, что для успешного выполнения сериализации класса должны быть выполнены два условия:

  • Класс должен реализовывать интерфейс java.io.Serializable.
  • Все поля в классе должны быть сериализуемыми. Если поле не сериализуемо, оно должно быть помечено как промежуточное.

Если вам интересно узнать, можно ли выполнить сериализацию стандартного класса Java, проверьте документацию по данному классу. Тест прост: если класс реализует java.io.Serializable, то он может быть подвергнут сериализации; в противном случае - не может.

Сериализация объекта

Класс ObjectOutputStream используется для выполнения сериализации объекта. Следующая программа SerializeDemo создает экземпляр объекта Employee и сериализует его в файл.

По завершению выполнения программы создается файл с именем employee.ser. Программа не генерирует никаких выходных данных, но изучает код и пытается определить, что совершает программа.

Примечание: в Java при сериализации объекта в файл установленным архитектурным требованием Java является присвоение файлу расширения .ser.

Пример

Десериализация объекта

Следующая программа DeserializeDemo выполняет в Java десериализацию объекта Employee, созданного в программе SerializeDemo. Изучите программу и попытайтесь определить ее выводимые данные.

Читайте также: