Рубрики
Без рубрики

Магические Методы Сериализации Java И Их Использование На Примере

В предыдущей статье Все, что вам нужно знать о сериализации Java, мы обсудили, как сериализовать… Помеченный java, сериализация.

В предыдущей статье Все, что Вам нужно знать о сериализации Java , мы обсудили, как сериализуемость класса обеспечивается за счет реализации Сериализуемого интерфейса. Если наш класс не реализует Сериализуемый интерфейс или если он содержит ссылку на класс, не Сериализуемый , то JVM выдаст исключение NotSerializableException .

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

Сериализуемый интерфейс – это интерфейс маркера, в котором нет методов или полей, и он работает как флаг для JVM. Процесс сериализации Java, предоставляемый ObjectInputStream и ObjectOutputStream классы полностью контролируются JVM.

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

Методы writeObject и readObject

Сериализуемые классы, которые хотят настроить или добавить некоторую дополнительную логику для улучшения обычного процесса сериализации/десериализации, должны предоставить writeObject и readObject методы с этими точными сигнатурами:

  • частный пустой объект записи(java.io . ObjectOutputStream out) вызывает исключение IOException
  • частный пустой объект чтения(java.io . ObjectInputStream в) вызывает исключение IOException, Исключение ClassNotFoundException

Эти методы уже подробно обсуждаются в статье Все, что Вам Нужно Знать О Сериализации Java .

Метод readObjectNoData для чтения объектов

Как описано в документах Java класса Сериализуемый , если мы хотим инициализировать состояние объекта для его конкретного класса в случае, если поток сериализации не перечисляет данный класс в качестве суперкласса десериализуемого объекта, мы должны предоставить writeObject и readObject методы с этими точными сигнатурами:

  • private void readObjectNoData() вызывает исключение ObjectStreamException

Это может произойти в случаях, когда принимающая сторона использует другую версию десериализованного класса экземпляра, чем отправляющая сторона, и версия получателя расширяет классы, которые не расширяются версией отправителя. Это также может произойти, если поток сериализации был изменен; следовательно, readObjectNoData полезен для правильной инициализации десериализованных объектов, несмотря на “враждебный” или неполный исходный поток.

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

Методы writeReplace и readResolve

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

  • Объект С ЛЮБЫМ МОДИФИКАТОРОМ ДОСТУПА writeReplace() вызывает исключение ObjectStreamException

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

  • Объект С ЛЮБЫМ МОДИФИКАТОРОМ ДОСТУПА readResolve() вызывает исключение ObjectStreamException

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

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

Вы можете прочитать больше о клонировании и сериализации Java на Темы клонирования Java и Сериализации Java .

Метод readResolve вызывается после Объект чтения вернулся (и наоборот Место записи вызывается до writeObject и, возможно, на другом объекте). Объект, возвращаемый методом, заменяет этот объект, возвращенный пользователю ObjectInputStream.readObject и любые дальнейшие обратные ссылки на объект в потоке. Мы можем использовать метод writeReplace для замены сериализующего объекта на null, чтобы ничего не было сериализовано, а затем использовать метод readResolve для замены десериализованного объекта экземпляром singleton.

Метод validateObject

Если мы хотим выполнить определенные проверки для некоторых наших полей, мы можем сделать это, реализовав интерфейс ObjectInputValidation и переопределив в нем метод validateobject .

Метод проверка объекта будет автоматически вызван, когда мы зарегистрируем эту проверку, вызвав ObjectInputStream.registerValidation(это, 0) из объекта чтения метод. Очень полезно убедиться, что поток не был изменен или что данные имеют смысл, прежде чем передавать их обратно в ваше приложение.

Приведенный ниже пример охватывает код для всех вышеперечисленных методов

public class SerializationMethodsExample {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Employee emp = new Employee("Naresh Joshi", 25);
        System.out.println("Object before serialization: " + emp.toString());

        // Serialization
        serialize(emp);

        // Deserialization
        Employee deserialisedEmp = deserialize();
        System.out.println("Object after deserialization: " + deserialisedEmp.toString());


        System.out.println();

        // This will print false because both object are separate
        System.out.println(emp == deserialisedEmp);

        System.out.println();

        // This will print false because both `deserialisedEmp` and `emp` are pointing to same object,
        // Because we replaced de-serializing object in readResolve method by current instance
        System.out.println(Objects.equals(emp, deserialisedEmp));
    }

    // Serialization code
    static void serialize(Employee empObj) throws IOException {
        try (FileOutputStream fos = new FileOutputStream("data.obj");
             ObjectOutputStream oos = new ObjectOutputStream(fos))
        {
            oos.writeObject(empObj);
        }
    }

    // Deserialization code
    static Employee deserialize() throws IOException, ClassNotFoundException {
        try (FileInputStream fis = new FileInputStream("data.obj");
             ObjectInputStream ois = new ObjectInputStream(fis))
        {
            return (Employee) ois.readObject();
        }
    }
}

class Employee implements Serializable, ObjectInputValidation {
    private static final long serialVersionUID = 2L;

    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // With ObjectInputValidation interface we get a validateObject method where we can do our validations.
    @Override
    public void validateObject() {
        System.out.println("Validating age.");

        if (age < 18 || age > 70)
        {
            throw new IllegalArgumentException("Not a valid age to create an employee");
        }
    }

    // Custom serialization logic,
    // This will allow us to have additional serialization logic on top of the default one e.g. encrypting object before serialization.
    private void writeObject(ObjectOutputStream oos) throws IOException {
        System.out.println("Custom serialization logic invoked.");
        oos.defaultWriteObject(); // Calling the default serialization logic
    }

    // Replacing de-serializing object with this,
    private Object writeReplace() throws ObjectStreamException {
        System.out.println("Replacing serialising object by this.");
        return this;
    }

    // Custom deserialization logic
    // This will allow us to have additional deserialization logic on top of the default one e.g. performing validations, decrypting object after deserialization.
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        System.out.println("Custom deserialization logic invoked.");

        ois.registerValidation(this, 0); // Registering validations, So our validateObject method can be called.

        ois.defaultReadObject(); // Calling the default deserialization logic.
    }

    // Replacing de-serializing object with this,
    // It will will not give us a full proof singleton but it will stop new object creation by deserialization.
    private Object readResolve() throws ObjectStreamException {
        System.out.println("Replacing de-serializing object by this.");
        return this;
    }

    @Override
    public String toString() {
        return String.format("Employee {name='%s', age='%s'}", name, age);
    }
}

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

Оригинал: “https://dev.to/njnareshjoshi/java-serialization-magic-methods-and-their-uses-with-example-4beo”