web-dev-qa-db-fra.com

Comment traiter Singleton avec la sérialisation

Considérez que j'ai une classe Singleton définie comme suit.

public class MySingleton implements Serializable{
 private static MySingleton myInstance;

 private MySingleton(){

 }
  static{
    myInstance =new MySingleton();
 }
 public static MySingleton getInstance(){
    return MySingleton.myInstance;
 }
}

Selon moi, la définition ci-dessus satisfait aux exigences d'un singleton. Le seul comportement supplémentaire ajouté est que la classe implémente une interface sérialisable.

Si une autre classe X récupère l'instance du single et l'écrit dans un fichier puis la désérialise pour obtenir une autre instance, nous aurons deux instances qui vont à l'encontre du principe de Singleton.

Comment puis-je éviter cela ou ai-je tort dans la définition ci-dessus?.

36
frictionlesspulley

La meilleure façon de faire est d’utiliser le modèle enum singleton:

public enum MySingleton {
  INSTANCE;
}

Cela garantit le caractère unique de l'objet et vous offre une possibilité de sérialisation de telle sorte que vous obteniez toujours la même instance.

Plus généralement, vous pouvez fournir une méthode readResolve() comme ceci:

protected Object readResolve() {
  return myInstance;
}
35
ColinD

@ColinD a en quelque sorte raison, mais sa réponse illustre également pourquoi les singletons ne gênent pas vraiment la sérialisation.

Voici ce qui se passe lorsque vous sérialisez une valeur enum (voir ici ).

Les règles pour la sérialisation d'une instance enum diffèrent de celles pour la sérialisation d'un objet sérialisable "ordinaire": la forme sérialisée d'une instance enum consiste uniquement en son nom de constante enum, avec des informations identifiant son type enum de base. Le comportement de désérialisation diffère également - les informations de classe sont utilisées pour trouver la classe enum appropriée, et la méthode Enum.valueOf est appelée avec cette classe et le nom de constante reçu afin d'obtenir la constante enum à renvoyer.

Ainsi, tout état supplémentaire que vous attachez à vos valeurs enum ne survivra pas à la sérialisation et à la désérialisation.

Vous pouvez faire la même chose vous-même en ajoutant un code de sérialisation/désérialisation personnalisé à vos classes singleton. Ce code devrait soit ne pas enregistrer l'état du singleton, soit le jeter lorsque le singleton est désérialisé. Dans les deux cas, vous mettriez la logique dans une méthode readResolve(), comme expliqué dans la réponse de @ ColinD.

Maintenant, je suppose que la raison pour laquelle vous voulez sérialiser des singletons est que vous voulez conserver leur état. Malheureusement, cela pose un problème conceptuel. Supposons que votre application ait instancié le singleton dans le cours normal des événements, puis désérialise un graphe d'objet contenant une copie d'une instance précédente du singleton. Qu'est-ce que ça peut faire?

  • S'il désérialise normalement le singleton, il viole "singleton-ness".
  • Si ce n'est pas le cas, l'application ne peut pas accéder à l'état précédent du singleton.
27
Stephen C

La solution avec enum ne fonctionnera pas avec des singletons gérés par Spring, EJB, Guice ou tout autre framework d’ID. Cela fonctionne uniquement avec les énumérations, uniquement parce que les énumérations sont traitées spécialement par l'algorithme de sérialisation.

Tout d'abord, singletons n'a pas besoin de la sérialisation, car si vous le désérialisiez, puis le singleton désérialisé! = YourSingleton.getInstance (), cela signifierait que vous avez deux instances de votre singleton, ce qui signifie que YourSingleton n'est pas singleton du tout, ce qui peut conduire à des bogues imprévisibles.

Cependant, il est parfois nécessaire de sérialiser des non-singleton qui contiennent une référence à singleton. La solution est simple:

class NonSingleton implements Serializable {
    private transient YourSingleton singleton = YourSingleton.getInstance();
    ...
}

Avec le printemps:

@Configurable
class NonSingleton implements Serializable {
    @Autowired
    private transient YourSingleton singleton;
    ...
}
5
iirekm

Voici ci-dessous ma classe Singleton qui implémente l'interface Serializable. Marquez qu'il contient la méthode readResolve() également.

import Java.io.Serializable;

public class Singleton implements Serializable {

    private static Singleton singleton = new Singleton( );

    public int i = 1;

    private Singleton() { }

    public static Singleton getInstance( ) {

       return singleton;
    }

    public Object readResolve() {
       return getInstance( );
    }

    public static void main(String[] args) {
        Singleton s1 = getInstance();
        System.out.println(s1.hashCode());

        Singleton s2 = getInstance();
        System.out.println(s2.hashCode());
    }
}

Vous trouverez ci-dessous la classe qui va d'abord sérialiser puis désérialiser la classe ci-dessus. Ici, la désérialisation a lieu deux fois, mais une seule fois, une seule fois sera créée à cause de la méthode readResolve (). 

public class SingletonSerializableDemo {

    static Singleton sing = Singleton.getInstance();
    static Singleton s1  = null;
    static Singleton s2 = null;
    public static void main(String[] args) {
        try {
             FileOutputStream fileOut =
             new FileOutputStream("E:/singleton.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut);
             out.writeObject(sing);
             out.close();
             fileOut.close();
             System.out.println("Serialized data is saved");

             FileInputStream fileIn1 = new FileInputStream("E:/singleton.ser");
             FileInputStream fileIn2 = new FileInputStream("E:/singleton.ser");
             ObjectInputStream in1 = new ObjectInputStream(fileIn1);
             ObjectInputStream in2 = new ObjectInputStream(fileIn2);
             s1 = (Singleton) in1.readObject();
             s2 = (Singleton) in2.readObject();
             System.out.println(s1.hashCode() + " "+ s1.i);
             s1.i = 10;
             System.out.println(s2.hashCode() + " "+ s2.i);
             in1.close();
             in2.close();
             fileIn1.close();
             fileIn2.close();
          }catch(Exception i) {
             i.printStackTrace();
          }
    }
}

Et le résultat sera:

Les données sérialisées sont enregistrées 
21061094 1 
21061094 10 

Conclusion: La classe Singleton peut également être sérialisée en conservant la méthode readResolve() dans la classe Singleton.

4
Rishi garg

Cela peut être une solution familière, mais juste au cas où pour référence.

public class ConnectionFactory implements Serializable {

    //Static variable for holding singleton reference object
    private static ConnectionFactory INSTANCE;

    /**
     * Private constructor
     */
    private ConnectionFactory() {

    }

    /**
     * Static method for fetching the instance
     *
     * @return
     */
    public static ConnectionFactory getIntance() {
        //Check whether instance is null or not
        if (INSTANCE == null) {
            //Locking the class object
            synchronized (ConnectionFactory.class) {
                //Doing double check for the instance
                //This is required in case first time two threads simultaneously invoke
                //getInstance().So when another thread get the lock,it should not create the
                //object again as its already created by the previous thread.
                if (INSTANCE == null) {
                    INSTANCE = new ConnectionFactory();
                }
            }
        }
        return INSTANCE;
    }

    /**
     * Special hook provided by serialization where developer can control what object needs to sent.
     * However this method is invoked on the new object instance created by de serialization process.
     *
     * @return
     * @throws ObjectStreamException
     */
    private Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }

}

Tester le code

public class SerializationTest {

    public static void main(String[] args) {
        ConnectionFactory INSTANCE = ConnectionFactory.getIntance();

        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("connectFactory.ser"));
            oos.writeObject(INSTANCE);
            oos.close();

            ObjectInputStream osi = new ObjectInputStream(new FileInputStream("connectFactory.ser"));
            ConnectionFactory factory1 = (ConnectionFactory) osi.readObject();
            osi.close();

            ObjectInputStream osi2 = new ObjectInputStream(new FileInputStream("connectFactory.ser"));
            ConnectionFactory factory2 = (ConnectionFactory) osi2.readObject();
            osi2.close();


            System.out.println("Instance reference check->" + factory1.getIntance());
            System.out.println("Instance reference check->" + factory2.getIntance());
            System.out.println("===================================================");
            System.out.println("Object reference check->" + factory1);
            System.out.println("Object reference check->" + factory2);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

}

Sortie

Instance reference check->com.javabrains.ConnectionFactory@6f94fa3e
Instance reference check->com.javabrains.ConnectionFactory@6f94fa3e
===================================================
Object reference check->com.javabrains.ConnectionFactory@6f94fa3e
Object reference check->com.javabrains.ConnectionFactory@6f94fa3e
1
Rajesh Samson

Disons que nous avons la classe singleton suivante:

public class ConnectionFactory implements Serializable {
    private static ConnectionFactory INSTANCE;

    private ConnectionFactory() {  }

    public static ConnectionFactory getInstance() {
        if (INSTANCE == null) {
            synchronized(ConnectionFactory.class) {
                if(INSTANCE == null)
                    INSTANCE = new ConnectionFactory();
            }
        }
        return INSTANCE;
    }
}

Nous avons maintenant la classe principale comme ci-dessous pour la sérialisation et la désérialisation d'objets:

 public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
     ConnectionFactory INSTANCE=ConnectionFactory.getInstance();
     ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("connFactory.ser"));  
     oos.writeObject(INSTANCE);  
     oos.close();

     // Here I am recreating the instance by reading the serialized object data store
     ObjectInputStream ois = new ObjectInputStream(new FileInputStream("connFactory.ser"));  
     ConnectionFactory factory1 = (ConnectionFactory) ois.readObject();  
     ois.close();  

     // I am recreating the instance AGAIN by reading the serialized object data store
     ObjectInputStream ois2 = new ObjectInputStream(new FileInputStream("connFactory.ser"));  
     ConnectionFactory factory2 = (ConnectionFactory) ois2.readObject();  
     ois2.close();

     // Let's see how we have broken the singleton behavior
     System.out.println("Instance reference check->" +factory1.getInstance());
     System.out.println("Instance reference check->" +factory2.getInstance());
     System.out.println("=========================================================");
     System.out.println("Object reference check->" + factory1);
     System.out.println("Object reference check->" + factory2);
}

Donc, si nous exécutons le code ci-dessus, nous aurons le comportement suivant: "il a créé deux objets et une référence statique pour INSTANCE. Cela signifie que si nous lisons le format sérialisé d’un objet singleton plusieurs fois, nous allons créer plusieurs objets. Ce n’est pas ce qu’un objet singleton est censé faire. Alors pouvons-nous éviter i ?, Oui, nous le pouvons. "

Pour éviter plusieurs instances de classe singleton, nous allons utiliser la méthode suivante fournie par la sérialisation:

private Object readResolve() throws ObjectStreamException {
    return INSTANCE;
}

Cela empêchera la création de plusieurs instances d'une classe singleton.

1
Aman Goel

Je pense que Singletons peut être sérialisé et voici le code sur la façon de le faire:


import Java.io.Serializable;

public class MySingleton implements Serializable {

    private MySingleton(String name) {
        this.name = name;
    }

    private static MySingleton mySingleton;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static MySingleton getInstance(String name) {
        if(mySingleton == null) {
            System.out.println("in if...");
            mySingleton = new MySingleton(name);
        }

        return mySingleton;
    }
}

et voici la méthode "main" qui obtient l'instance de la classe Singleton ci-dessus, la sérialise et la désérialise:



 public static void main (String[] args) {

        MySingleton m = MySingleton.getInstance("Akshay");

        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://temp.ser"));
            oos.writeObject(m);

            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D://temp.ser"));
            MySingleton m2 = (MySingleton) ois.readObject();
            System.out.println(m2.getName());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

et la sortie est: -

dans si ...

Akshay

Merci.

0
Akshay Lokur

Voici la réponse pour Breaking the Singleton et comment empêcher notre classe de créer un objet différent en utilisant readResolve () methood;

importer Java.io.Serializable;

classe publique Singleton implémente Serializable {

private static final long serialVersionUID = 1L;

private Singleton() {
}

private static class SingletonHelper {

    private static final Singleton INSTANCE = new Singleton();

}

public static Singleton getInstance() {

    return SingletonHelper.INSTANCE;
}

private Object readResolve() {
    Singleton instance = getInstance();
    return instance;
}

}

classe publique BreakSIngletonUsingSerialization {

public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {

    Singleton demo1 =Singleton.getInstance();
    ObjectOutput out = new ObjectOutputStream(new FileOutputStream("C:/Eclipse/serial.ser"));
    out.writeObject(demo1);
    Singleton demo2 =null;
    ObjectInput in = new ObjectInputStream(new FileInputStream("C:/Eclipse/serial.ser"));

    demo2 = (Singleton)in.readObject();

    System.out.println("Hascode demo1 : " +demo1);
    System.out.println("Hascode demo2 : " +demo2);
}

}

0
Abhinav Katyayen