Статьи

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
    }
 
    ...
}

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

1
2
3
4
5
6
7
8
public class ConvenientPoint extends CumbersomePoint implements Serializable {
 
    public ConvenientPoint(double x, double y, String name) {
        super(x, y, name);
    }
 
    ...
}

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
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 :

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

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

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

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

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

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

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