[JAVA] Élément 90: considérez les proxies de sérialisation au lieu des instances sérialisées

90. Considérez un proxy sérialisé au lieu d'une instance sérialisée

Le modèle de proxy de sérialisation est une technique qui réduit considérablement les risques décrits dans ce chapitre.

Modèle de proxy de sérialisation

Dans le modèle de proxy de sérialisation, créez d'abord une classe interne statique privée. Cette classe interne (appelée proxy de sérialisation) représente de manière concise l'état logique d'une instance de la classe englobante.

Le proxy de sérialisation a un constructeur, dont l'argument est la classe englobante. Ce constructeur copie simplement les données des arguments, sans avoir besoin de contrôles de validation ou de copies défensives.

Intentionnellement, la forme sérialisée par défaut du proxy de sérialisation doit être la forme sérialisée complète de la classe englobante. La classe englobante et le proxy de sérialisation doivent implémenter Serializable.

Voici un exemple du proxy de sérialisation de la classe Period vue dans Item50 et Item88.

//Classe imbriquée. Dédié à la sérialisation.
private static class SerializationProxy implements Serializable {
    //Le champ peut être définitif
    private final Date start;
    private final Date end;

    SerializationProxy(Period period) {
        //Pas besoin de copie défensive
        this.start = period.start;
        this.end = period.end;
    }

    //Définir l'UID (Item87)
    private static final long serialVersionUID = 234098243823485285L;
}

Ensuite, ajoutez la méthode writeReplace à la classe englobante. Un exemple est ci-dessous.

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

Avec cette méthode, la classe englobante indique clairement au système de sérialisation que la cible de sérialisation est le proxy de sérialisation.

En plaçant le writeReplace ci-dessus dans la classe englobante, l'instance de la classe englobante ne doit pas être sérialisée, mais il est nécessaire de se protéger contre le cas où un attaquant rompt la condition immuable et charge la classe englobante. y a-t-il.

Par conséquent, placez la méthode readObject suivante dans la classe englobante.

//Méthode ReadObject de la classe à sérialiser
private void readObject(ObjectInputStream stream) throw InvalidObjectException {
    throw new InvalidObjectException("Proxy required");
}

En faisant cela, la version sérialisée de la classe englobante ne sera pas chargée.

Enfin, placez la méthode readResolve sur le proxy de sérialisation. Cette méthode readResolve renvoie une instance qui est logiquement égale à la classe englobante. En raison de l'existence de cette méthode, ce qui a été converti en proxy de sérialisation au moment de la sérialisation sera renvoyé sous la forme de la classe englobante au moment de la désérialisation.

La méthode readResolve utilise uniquement l'API publique de la classe englobante pour créer une instance de la classe englobante, ce qui montre la qualité de ce modèle. Cela efface de nombreuses fonctionnalités de la sérialisation. En effet, les instances désérialisées seront créées de la même manière que les constructeurs, les usines statiques, etc. Si le constructeur, l'usine statique, etc. respectent les conditions immuables, l'instance désérialisée protège également les conditions immuables.

La méthode readResolve du proxy de sérialisation Period est la suivante.

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

Semblable à la méthode de copie défensive, le proxy de sérialisation peut empêcher les attaques par de faux flux d'octets et des attaques de vol de terrain. Contrairement à la méthode décrite ci-dessus, le proxy de sérialisation permet aux champs d'être finaux et aux classes d'être immuables (Item17). De plus, avec un proxy de sérialisation, vous n'avez pas à penser aux champs qui peuvent être autorisés à être attaqués et aux vérifications de validation explicites lors de la désérialisation.

Un autre avantage de ce modèle est que l'instance lorsqu'elle est sérialisée et l'instance lorsqu'elle est désérialisée peuvent avoir des classes différentes. Considérez EnumSet (Item36). Dans cette classe, il n'y a pas de constructeur, seulement des méthodes de fabrique. Du point de vue du client, la méthode de fabrique renvoie EnumSet, mais la sous-classe RegularEnumSet ou JumboEnumSet est renvoyée (selon le nombre d'éléments, 64 ou moins est Regular). Envisagez maintenant de sérialiser un ensemble d'énumérations avec 60 éléments, d'ajouter 5 éléments au type enum et de désérialiser l'ensemble d'énumérations. Il s'agissait d'une instance RegularEnumSet lorsqu'elle a été sérialisée, mais lorsqu'elle est désérialisée, le JumboEnumSet est de meilleure taille. Pour cette implémentation, EnumSet utilise en fait le mécanisme de proxy de sérialisation. Plus précisément, c'est comme suit.

    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;
    }

Le modèle de proxy de sérialisation a deux limitations.

En outre, si le modèle de proxy de sérialisation est utilisé, les performances seront légèrement dégradées.

Recommended Posts

Élément 90: considérez les proxies de sérialisation au lieu des instances sérialisées
Point 87: envisagez d'utiliser un formulaire sérialisé personnalisé
[Lire Java efficace] Chapitre 2 Item 1 "Considérez les méthodes de fabrique statiques plutôt que les constructeurs"
Élément 36: utiliser EnumSet au lieu de champs de bits
Point 37: Utilisez EnumMap au lieu de l'indexation ordinale