Как бороться с синглтоном вместе с сериализацией - PullRequest
38 голосов
/ 14 октября 2010

Предположим, у меня есть класс Singleton, определенный следующим образом.

public class MySingleton implements Serializable{
 private static MySingleton myInstance;

 private MySingleton(){

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

Приведенное мной выше определение удовлетворяет требованиям Singleton. Единственное добавленное поведение заключается в том, что класс реализует сериализуемый интерфейс.

Если другой класс X получит экземпляр сингла и запишет его в файл, а затем десериализует его для получения другого экземпляра, у нас будет два экземпляра, что противоречит принципу Синглтона.

Как мне избежать этого или я ошибаюсь в самом приведенном выше определении.

Ответы [ 9 ]

36 голосов
/ 14 октября 2010

Лучший способ сделать это - использовать шаблон enum singleton:

public enum MySingleton {
  INSTANCE;
}

Это гарантирует единственность объекта и обеспечивает сериализуемость для вас таким образом, что вы всегда получаете один и тот же экземпляр.

В более общем смысле вы можете указать метод readResolve(), например:

protected Object readResolve() {
  return myInstance;
}
27 голосов
/ 14 октября 2010

@ ColinD вроде бы правильно, но его ответ также иллюстрирует, почему синглтоны не превращают сериалы в желе.

Вот что происходит, когда вы сериализуете значение перечисления (см. здесь ).

Правила сериализации экземпляра enum отличаются от правил сериализации «обычного» сериализуемого объекта: сериализованная форма экземпляра enum состоит только из имени константы enum и информации, идентифицирующей его базовый тип enum. Поведение десериализации также отличается - информация о классе используется для поиска соответствующего класса перечисления, и метод Enum.valueOf вызывается с этим классом и полученным именем константы для получения возвращаемой константы перечисления.

Таким образом, любое дополнительное состояние, которое вы присоединяете к значениям перечисления, не сохраняется после сериализации и десериализации.

Вы можете сделать то же самое самостоятельно, добавив собственный код сериализации / десериализации в свои одноэлементные классы. Этот код должен либо вообще не записывать состояние синглтона, либо выбрасывать его при десериализации. В любом случае, вы бы поместили логику в метод readResolve(), как объясняется ответом @ ColinD.

Теперь я предполагаю, что причина, по которой вы хотите сериализовать синглтоны, заключается в том, что вы хотите сохранить их состояние. К сожалению, это представляет концептуальную проблему. Предположим, что ваше приложение создало экземпляр синглтона в обычном ходе событий, а затем десериализует некоторый граф объектов, который включает в себя копию предыдущего экземпляра синглтона. Что это может сделать?

  • Если он десериализует синглтон обычным образом, он нарушает "синглтон-несс".
  • Если это не так, приложение не может получить доступ к предыдущему состоянию синглтона.
5 голосов
/ 14 октября 2010

Решение с enum не будет работать с Singletons, управляемыми Spring, EJB, Guice или любой другой DI-инфраструктурой. Он работает только с перечислениями, только потому, что перечисление обрабатывается специально алгоритмом сериализации.

Во-первых, синглетам не требуется сериализация , потому что если вы десериализовали его, а затем десериализовали синглтон! = YourSingleton.getInstance (), это означало бы, что у вас есть два экземпляра вашего синглтона, что означает что YourSingleton вовсе не синглтон, что может привести к непредсказуемым ошибкам.

Однако иногда вам нужно сериализовать non-singleton, который содержит ссылку на singleton. Решение простое:

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

с пружиной:

@Configurable
class NonSingleton implements Serializable {
    @Autowired
    private transient YourSingleton singleton;
    ...
}
4 голосов
/ 01 октября 2016

Здесь ниже мой класс Singleton, который реализует интерфейс Serializable.Отметьте, что он также содержит метод readResolve().

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

Ниже приведен класс, который сначала сериализует, а затем десериализует вышеуказанный класс.Здесь десериализация происходит два раза, но оба раза будет создан только один экземпляр из-за метода 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();
          }
    }
}

И вывод будет:

Сериализованные данные сохраняются
21061094 1
21061094 10

Вывод: Класс Singleton также можно сериализовать, сохранив метод readResolve() в классе Singleton.

1 голос
/ 31 июля 2017

Допустим, у нас есть следующий одноэлементный класс:

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

Теперь у нас есть основной класс, как показано ниже для сериализации и десериализации объектов:

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

Так что, если мы выполним приведенный выше кодмы получим следующее поведение: "он создал два объекта и одну статическую ссылку для INSTANCE. Это означает, что если мы прочитаем сериализованный формат одноэлементного объекта несколько раз, мы создадим несколько объектов. Это не то, что одноэлементный объектдолжен делать. Поэтому можем ли мы избежать i ?, Да, мы можем. "

Чтобы избежать нескольких экземпляров одноэлементного класса, мы будем использовать следующий метод, предоставляемый сериализацией:

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

Это предотвратит создание нескольких экземпляров одноэлементного класса.

1 голос
/ 17 мая 2017

Это может быть знакомое решение, но на всякий случай для справки.

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

}

Тестирование кода

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

    }

}

выход

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
0 голосов
/ 07 февраля 2019

Вот ответ на вопрос о разрыве класса Singleton и о том, как запретить нашему классу создавать различные объекты с помощью метода readResolve ();

import java.io.Serializable;

publicКласс Singleton реализует Сериализуемый {

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

}

открытый класс 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 голосов
/ 29 апреля 2014

Я думаю, что Singletons можно сериализовать, и вот код, как это сделать:


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

и вот "основной" метод, который получает экземпляр класса Singleton выше, сериализует и десериализует его:



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

    }

и вывод: -

в если ...

Акшай

Спасибо.

0 голосов
/ 07 сентября 2012

Классы Singleton похожи на менеджер или контроллер, и в общем случае мы не хотим сохранять состояние какого-либо контроллера вместо сущности.Как правило, нам нужно сохранять состояние объекта любой сущности, а не контроллера.
Singleton - это синглтон для загрузчика одного класса, а не для загрузчика нескольких классов.Если класс загружается в загрузчик классов, то другой загрузчик классов не будет знать об этом, поэтому он ведет себя так.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...