[JAVA] Punkt 90: Betrachten Sie Serialisierungs-Proxys anstelle von serialisierten Instanzen

90. Betrachten Sie einen serialisierten Proxy anstelle einer serialisierten Instanz

Das Serialisierungs-Proxy-Muster ist eine Technik, die die in diesem Kapitel beschriebenen Risiken erheblich reduziert.

Serialisierungs-Proxy-Muster

Erstellen Sie im Serialisierungs-Proxy-Muster zunächst eine private statische innere Klasse. Diese innere Klasse (als Serialisierungs-Proxy bezeichnet) ist eine übersichtliche Darstellung des logischen Zustands einer Instanz der einschließenden Klasse.

Der Serialisierungs-Proxy verfügt über einen Konstruktor, dessen Argument die einschließende Klasse ist. Dieser Konstruktor kopiert einfach die Daten aus den Argumenten, ohne dass Validierungsprüfungen oder defensive Kopien erforderlich sind.

Absichtlich sollte die standardmäßige serialisierte Form des Serialisierungs-Proxys die vollständig serialisierte Form der einschließenden Klasse sein. Sowohl die einschließende Klasse als auch der Serialisierungs-Proxy sollten Serializable implementieren.

Das folgende Beispiel zeigt den Serialisierungs-Proxy der Period-Klasse in Item50 und Item88.

//Verschachtelte Klasse. Speziell für die Serialisierung.
private static class SerializationProxy implements Serializable {
    //Feld kann endgültig sein
    private final Date start;
    private final Date end;

    SerializationProxy(Period period) {
        //Keine defensive Kopie erforderlich
        this.start = period.start;
        this.end = period.end;
    }

    //UID einstellen (Item87)
    private static final long serialVersionUID = 234098243823485285L;
}

Fügen Sie als Nächstes die Methode writeReplace zur einschließenden Klasse hinzu. Ein Beispiel ist unten.

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

Bei dieser Methode macht die einschließende Klasse dem Serialisierungssystem klar, dass das Serialisierungsziel der Serialisierungsproxy ist.

Wenn Sie den obigen writeReplace in die einschließende Klasse einfügen, sollte die Instanz der einschließenden Klasse nicht serialisiert werden. Sie muss jedoch vor dem Fall geschützt werden, in dem ein Angreifer die unveränderliche Bedingung verletzt und die einschließende Klasse lädt. Gibt es.

Fügen Sie daher die folgende readObject-Methode in die einschließende Klasse ein.

//ReadObject-Methode der zu serialisierenden Klasse
private void readObject(ObjectInputStream stream) throw InvalidObjectException {
    throw new InvalidObjectException("Proxy required");
}

Auf diese Weise wird die serialisierte Version der einschließenden Klasse nicht geladen.

Platzieren Sie abschließend die readResolve-Methode auf dem Serialisierungs-Proxy. Diese readResolve-Methode gibt eine Instanz zurück, die logisch der einschließenden Klasse entspricht. Aufgrund der Existenz dieser Methode wird das, was zum Zeitpunkt der Serialisierung in den Serialisierungs-Proxy konvertiert wurde, zum Zeitpunkt der Deserialisierung in die Form der einschließenden Klasse zurückgegeben.

Die readResolve-Methode verwendet nur die öffentliche API der einschließenden Klasse, um eine Instanz der einschließenden Klasse zu erstellen, die die Güte dieses Musters zeigt. Dadurch werden viele Funktionen der Serialisierung gelöscht. Dies liegt daran, dass deserialisierte Instanzen auf dieselbe Weise wie Konstruktoren, statische Fabriken usw. erstellt werden. Wenn der Konstruktor, die statische Fabrik usw. die unveränderlichen Bedingungen erfüllen, schützt die deserialisierte Instanz auch die unveränderlichen Bedingungen.

Die readResolve-Methode des Period-Serialisierungs-Proxys lautet wie folgt.

private Object readResolve() {
    return new Period(start, end);
}

Ähnlich wie bei der defensiven Kopiermethode kann der Serialisierungs-Proxy Angriffe durch gefälschte Byte-Streams und Angriffe auf Felddiebstahl verhindern. Im Gegensatz zu der oben beschriebenen Methode ermöglicht der Serialisierungs-Proxy, dass Felder endgültig und Klassen unveränderlich sind (Punkt 17). Außerdem müssen Sie bei einem Serialisierungs-Proxy nicht darüber nachdenken, welche Felder angegriffen werden dürfen, und während der Deserialisierung explizite Validierungsprüfungen durchführen.

Ein weiterer Vorteil dieses Musters besteht darin, dass die Instanz bei der Serialisierung und die Instanz bei der Deserialisierung unterschiedliche Klassen haben können. Betrachten Sie EnumSet (Item36). In dieser Klasse gibt es keine Konstruktoren, nur Factory-Methoden. Aus Sicht des Clients gibt die Factory-Methode EnumSet zurück, aber entweder die Unterklasse RegularEnumSet oder JumboEnumSet wird zurückgegeben (abhängig von der Anzahl der Elemente ist 64 oder weniger Regular). Betrachten Sie nun die Serialisierung eines Aufzählungssatzes mit 60 Elementen, das Hinzufügen von 5 Elementen zum Aufzählungstyp und das Deserialisieren des Aufzählungssatzes. Wenn es serialisiert wurde, war es eine RegularEnumSet-Instanz, aber wenn es deserialisiert ist, hat das JumboEnumSet eine bessere Größe. Für diese Implementierung verwendet EnumSet tatsächlich den Serialisierungs-Proxy-Mechanismus. Insbesondere ist es wie folgt.

    private static class SerializationProxy<E extends Enum<E>>
        implements java.io.Serializable
    {

        private static final Enum<?>[] ZERO_LENGTH_ENUM_ARRAY = new Enum<?>[0];

        /**
         * The element type of this enum set.
         *
         * @serial
         */
        private final Class<E> elementType;

        /**
         * The elements contained in this enum set.
         *
         * @serial
         */
        private final Enum<?>[] elements;

        SerializationProxy(EnumSet<E> set) {
            elementType = set.elementType;
            elements = set.toArray(ZERO_LENGTH_ENUM_ARRAY);
        }

        /**
         * Returns an {@code EnumSet} object with initial state
         * held by this proxy.
         *
         * @return a {@code EnumSet} object with initial state
         * held by this proxy
         */
        @SuppressWarnings("unchecked")
        private Object readResolve() {
            // instead of cast to E, we should perhaps use elementType.cast()
            // to avoid injection of forged stream, but it will slow the
            // implementation
            EnumSet<E> result = EnumSet.noneOf(elementType);
            for (Enum<?> e : elements)
                result.add((E)e);
            return result;
        }

        private static final long serialVersionUID = 362491234563181265L;
    }

Das Serialisierungs-Proxy-Muster weist zwei Einschränkungen auf.

Wenn das Serialisierungs-Proxy-Muster verwendet wird, wird die Leistung geringfügig beeinträchtigt.

Recommended Posts

Punkt 90: Betrachten Sie Serialisierungs-Proxys anstelle von serialisierten Instanzen
Punkt 87: Erwägen Sie die Verwendung eines benutzerdefinierten serialisierten Formulars
[Read Effective Java] Kapitel 2 Punkt 1 "Betrachten Sie statische Factory-Methoden anstelle von Konstruktoren"
Punkt 36: Verwenden Sie EnumSet anstelle von Bitfeldern
Punkt 37: Verwenden Sie EnumMap anstelle der ordinalen Indizierung