Статьи

Пример шаблона прокси-сервера сериализации


Есть книги, которые очень сильно изменят вашу жизнь.
Одна из таких книг является 
«Эффективное Java»  от 
Джошуа Блох . Ниже вы можете найти небольшой эксперимент, который был вдохновлен главой 11 этой книги — «Сериализация».

Предположим, что у нас есть класс, предназначенный для наследования, который сам не является 
Serializable  и не имеет конструктора без параметров, как в этом примере:

public class CumbersomePoint {

    private String name;

    private double x;

    private double y;

    protected CumbersomePoint(double x, double y, String name) {
        this.x = x;
        this.y = y;
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    ...
}

Теперь, когда мы расширяем этот класс, например, следующим образом:

public class ConvenientPoint extends CumbersomePoint implements Serializable {

    public ConvenientPoint(double x, double y, String name) {
        super(x, y, name);
    }

    ...
}

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

Начнем с добавления в 
 класс
ConvenientPoint следующего внутреннего класса:

private static class SerializationProxy implements Serializable {

        private String name;

        private double x;

        private double y;

        public SerializationProxy(ConvenientPoint point) {
            this.name = point.getName();
            this.x = point.getX();
            this.y = point.getY();
        }

        private Object readResolve() {
            return new ConvenientPoint(x, y, name);
        }

    }

Класс 
SerializationProxy  будет представлять логическое состояние экземпляра включающего класса. Придется добавить также следующий метод в 
 класс
ConvenientPoint :

   private Object writeReplace() {
        return new SerializationProxy(this);
    }

Теперь, когда 
 экземпляр
ConvenientPoint будет сериализован, он назначит его замену благодаря 
методу
writeReplace — 
 экземпляр
SerializationProxy будет сериализован вместо 
ConvenientPoint .

С другой стороны, когда 
SerializationProxy  будет десериализован, 
использование  метода readResolve назначит его замену, являющуюся 
 экземпляром
ConvenientPoint .

Как видите, мы сделали 
 сериализуемую
ConvenientPoint вне зависимости от отсутствующего конструктора без параметров в не сериализуемом родительском классе.

Еще одно замечание в конце этого поста — если вы хотите защитить от взлома инвариантов класса, примененного конструктором, вы можете добавить следующий метод к классу, используя 
Serialization Proxy Pattern  (
ConvenientPoint  в нашем примере): 

   private void readObject(ObjectInputStream stream) throws InvalidObjectException {
        throw new InvalidObjectException("Use Serialization Proxy instead.");
    }

Это предотвратит десериализацию окружающего класса.