📜  如何防止单例模式的反射、序列化和克隆?

📅  最后修改于: 2021-09-10 02:45:48             🧑  作者: Mango

先决条件:单例模式
在本文中,我们将看到哪些概念可以破坏类的单例属性以及如何避免它们。主要有 3 个概念可以破坏类的单例属性。让我们一一讨论它们。

  1. 反射:反射会破坏单例类的单例属性,如下例所示:
JAVA
// Java code to explain effect of Reflection
// on Singleton property
 
import java.lang.reflect.Constructor;
 
// Singleton class
class Singleton
{
    // public instance initialized when loading the class
    public static Singleton instance = new Singleton();
     
    private Singleton()
    {
        // private constructor
    }
}
 
public class GFG
{
 
    public static void main(String[] args)
    {
        Singleton instance1 = Singleton.instance;
        Singleton instance2 = null;
        try
        {
            Constructor[] constructors =
                    Singleton.class.getDeclaredConstructors();
            for (Constructor constructor : constructors)
            {
                // Below code will destroy the singleton pattern
                constructor.setAccessible(true);
                instance2 = (Singleton) constructor.newInstance();
                break;
            }
        }
     
        catch (Exception e)
        {
            e.printStackTrace();
        }
         
    System.out.println("instance1.hashCode():- "
                                      + instance1.hashCode());
    System.out.println("instance2.hashCode():- "
                                      + instance2.hashCode());
    }
}


JAVA
//Java program for Enum type singleton
public enum Singleton
{
  INSTANCE;
}


JAVA
// Java code to explain effect of
// Serialization on singleton classes
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
class Singleton implements Serializable
{
    // public instance initialized when loading the class
    public static Singleton instance = new Singleton();
     
    private Singleton()
    {
        // private constructor
    }
}
 
 
public class GFG
{
 
    public static void main(String[] args)
    {
        try
        {
            Singleton instance1 = Singleton.instance;
            ObjectOutput out
                = new ObjectOutputStream(new FileOutputStream("file.text"));
            out.writeObject(instance1);
            out.close();
     
            // deserialize from file to object
            ObjectInput in
                = new ObjectInputStream(new FileInputStream("file.text"));
             
            Singleton instance2 = (Singleton) in.readObject();
            in.close();
     
            System.out.println("instance1 hashCode:- "
                                                 + instance1.hashCode());
            System.out.println("instance2 hashCode:- "
                                                 + instance2.hashCode());
        }
         
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}


JAVA
// Java code to remove the effect of
// Serialization on singleton classes
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
class Singleton implements Serializable
{
    // public instance initialized when loading the class
    public static Singleton instance = new Singleton();
     
    private Singleton()
    {
        // private constructor
    }
     
    // implement readResolve method
    protected Object readResolve()
    {
        return instance;
    }
}
 
public class GFG
{
 
    public static void main(String[] args)
    {
        try
        {
            Singleton instance1 = Singleton.instance;
            ObjectOutput out
                = new ObjectOutputStream(new FileOutputStream("file.text"));
            out.writeObject(instance1);
            out.close();
         
            // deserialize from file to object
            ObjectInput in
                = new ObjectInputStream(new FileInputStream("file.text"));
            Singleton instance2 = (Singleton) in.readObject();
            in.close();
         
            System.out.println("instance1 hashCode:- "
                                           + instance1.hashCode());
            System.out.println("instance2 hashCode:- "
                                           + instance2.hashCode());
        }
         
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}


JAVA
// JAVA code to explain cloning
// issue with singleton
class SuperClass implements Cloneable
{
  int i = 10;
 
  @Override
  protected Object clone() throws CloneNotSupportedException
  {
    return super.clone();
  }
}
 
// Singleton class
class Singleton extends SuperClass
{
  // public instance initialized when loading the class
  public static Singleton instance = new Singleton();
 
  private Singleton()
  {
    // private constructor
  }
}
 
public class GFG
{
  public static void main(String[] args) throws CloneNotSupportedException
  {
    Singleton instance1 = Singleton.instance;
    Singleton instance2 = (Singleton) instance1.clone();
    System.out.println("instance1 hashCode:- "
                           + instance1.hashCode());
    System.out.println("instance2 hashCode:- "
                           + instance2.hashCode());
  }
}


JAVA
// JAVA code to explain overcome
// cloning issue with singleton
class SuperClass implements Cloneable
{
  int i = 10;
 
  @Override
  protected Object clone() throws CloneNotSupportedException
  {
    return super.clone();
  }
}
 
// Singleton class
class Singleton extends SuperClass
{
  // public instance initialized when loading the class
  public static Singleton instance = new Singleton();
 
  private Singleton()
  {
    // private constructor
  }
 
  @Override
  protected Object clone() throws CloneNotSupportedException
  {
    throw new CloneNotSupportedException();
  }
}
 
public class GFG
{
  public static void main(String[] args) throws CloneNotSupportedException
  {
    Singleton instance1 = Singleton.instance;
    Singleton instance2 = (Singleton) instance1.clone();
    System.out.println("instance1 hashCode:- "
                         + instance1.hashCode());
    System.out.println("instance2 hashCode:- "
                         + instance2.hashCode());
  }
}


JAVA
// JAVA code to explain overcome
// cloning issue with singleton
class SuperClass implements Cloneable
{
  int i = 10;
 
  @Override
  protected Object clone() throws CloneNotSupportedException
  {
    return super.clone();
  }
}
 
// Singleton class
class Singleton extends SuperClass
{
  // public instance initialized when loading the class
  public static Singleton instance = new Singleton();
 
  private Singleton()
  {
    // private constructor
  }
 
  @Override
  protected Object clone() throws CloneNotSupportedException
  {
    return instance;
  }
}
 
public class GFG
{
  public static void main(String[] args) throws CloneNotSupportedException
  {
    Singleton instance1 = Singleton.instance;
    Singleton instance2 = (Singleton) instance1.clone();
    System.out.println("instance1 hashCode:- "
                           + instance1.hashCode());
    System.out.println("instance2 hashCode:- "
                           + instance2.hashCode());
  }
}


Output:-
instance1.hashCode():- 366712642
instance2.hashCode():- 1829164700
  1. 运行这个类后,你会看到 hashCode 不同,这意味着创建了同一个类的 2 个对象,并且单例模式已被销毁。
    克服反射问题:为了克服反射引起的问题,使用枚举,因为Java 在内部确保枚举值仅实例化一次。由于Java枚举是全局可访问的,因此它们可用于单例。它唯一的缺点是它不灵活,即它不允许延迟初始化。

Java

//Java program for Enum type singleton
public enum Singleton
{
  INSTANCE;
}
  1. 由于枚举没有任何构造函数,因此反射不可能使用它。枚举有它们的默认构造函数,我们不能自己调用它们。 JVM 在内部处理枚举构造函数的创建和调用。由于枚举不向程序提供其构造函数定义,因此我们也无法通过反射访问它们。因此,在枚举的情况下,反射不能破坏单例属性。
  2. 序列化:-序列化还会导致单例类的单例属性损坏。序列化用于转换字节流的对象并保存在文件中或通过网络发送。假设您序列化了一个单例类的对象。然后,如果您反序列化该对象,它将创建一个新实例,从而打破单例模式。

Java

// Java code to explain effect of
// Serialization on singleton classes
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
class Singleton implements Serializable
{
    // public instance initialized when loading the class
    public static Singleton instance = new Singleton();
     
    private Singleton()
    {
        // private constructor
    }
}
 
 
public class GFG
{
 
    public static void main(String[] args)
    {
        try
        {
            Singleton instance1 = Singleton.instance;
            ObjectOutput out
                = new ObjectOutputStream(new FileOutputStream("file.text"));
            out.writeObject(instance1);
            out.close();
     
            // deserialize from file to object
            ObjectInput in
                = new ObjectInputStream(new FileInputStream("file.text"));
             
            Singleton instance2 = (Singleton) in.readObject();
            in.close();
     
            System.out.println("instance1 hashCode:- "
                                                 + instance1.hashCode());
            System.out.println("instance2 hashCode:- "
                                                 + instance2.hashCode());
        }
         
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}
Output:- 
instance1 hashCode:- 1550089733
instance2 hashCode:- 865113938
  1. 如您所见,两个实例的 hashCode 是不同的,因此有 2 个单例类的对象。因此,该类不再是单例。
    克服序列化问题:-为了克服这个问题,我们必须实现方法 readResolve() 方法。

Java

// Java code to remove the effect of
// Serialization on singleton classes
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
class Singleton implements Serializable
{
    // public instance initialized when loading the class
    public static Singleton instance = new Singleton();
     
    private Singleton()
    {
        // private constructor
    }
     
    // implement readResolve method
    protected Object readResolve()
    {
        return instance;
    }
}
 
public class GFG
{
 
    public static void main(String[] args)
    {
        try
        {
            Singleton instance1 = Singleton.instance;
            ObjectOutput out
                = new ObjectOutputStream(new FileOutputStream("file.text"));
            out.writeObject(instance1);
            out.close();
         
            // deserialize from file to object
            ObjectInput in
                = new ObjectInputStream(new FileInputStream("file.text"));
            Singleton instance2 = (Singleton) in.readObject();
            in.close();
         
            System.out.println("instance1 hashCode:- "
                                           + instance1.hashCode());
            System.out.println("instance2 hashCode:- "
                                           + instance2.hashCode());
        }
         
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}
Output:- 
instance1 hashCode:- 1550089733
instance2 hashCode:- 1550089733
  1. 以上两个哈希码相同,因此不会创建其他实例。
  2. 克隆:克隆是一个创建重复对象的概念。使用 clone 我们可以创建对象的副本。假设,我们创建了一个单例对象的克隆,然后它会创建一个副本,其中有一个单例类的两个实例,因此该类不再是单例了。

Java

// JAVA code to explain cloning
// issue with singleton
class SuperClass implements Cloneable
{
  int i = 10;
 
  @Override
  protected Object clone() throws CloneNotSupportedException
  {
    return super.clone();
  }
}
 
// Singleton class
class Singleton extends SuperClass
{
  // public instance initialized when loading the class
  public static Singleton instance = new Singleton();
 
  private Singleton()
  {
    // private constructor
  }
}
 
public class GFG
{
  public static void main(String[] args) throws CloneNotSupportedException
  {
    Singleton instance1 = Singleton.instance;
    Singleton instance2 = (Singleton) instance1.clone();
    System.out.println("instance1 hashCode:- "
                           + instance1.hashCode());
    System.out.println("instance2 hashCode:- "
                           + instance2.hashCode());
  }
}
Output :- 
instance1 hashCode:- 366712642
instance2 hashCode:- 1829164700
  1. 两个不同的 hashCode 意味着有 2 个不同的单例类对象。
    克服克隆问题:-为了克服这个问题,覆盖 clone() 方法并从克隆方法抛出异常 CloneNotSupportedException。现在每当用户尝试创建单例对象的克隆时,它都会抛出异常,因此我们的类保持单例。

Java

// JAVA code to explain overcome
// cloning issue with singleton
class SuperClass implements Cloneable
{
  int i = 10;
 
  @Override
  protected Object clone() throws CloneNotSupportedException
  {
    return super.clone();
  }
}
 
// Singleton class
class Singleton extends SuperClass
{
  // public instance initialized when loading the class
  public static Singleton instance = new Singleton();
 
  private Singleton()
  {
    // private constructor
  }
 
  @Override
  protected Object clone() throws CloneNotSupportedException
  {
    throw new CloneNotSupportedException();
  }
}
 
public class GFG
{
  public static void main(String[] args) throws CloneNotSupportedException
  {
    Singleton instance1 = Singleton.instance;
    Singleton instance2 = (Singleton) instance1.clone();
    System.out.println("instance1 hashCode:- "
                         + instance1.hashCode());
    System.out.println("instance2 hashCode:- "
                         + instance2.hashCode());
  }
}
Output:-
Exception in thread "main" java.lang.CloneNotSupportedException
    at GFG.Singleton.clone(GFG.java:29)
    at GFG.GFG.main(GFG.java:38)
  1. 现在我们已经停止用户创建单例类的克隆。如果你不想抛出异常,你也可以从 clone 方法返回相同的实例。

Java

// JAVA code to explain overcome
// cloning issue with singleton
class SuperClass implements Cloneable
{
  int i = 10;
 
  @Override
  protected Object clone() throws CloneNotSupportedException
  {
    return super.clone();
  }
}
 
// Singleton class
class Singleton extends SuperClass
{
  // public instance initialized when loading the class
  public static Singleton instance = new Singleton();
 
  private Singleton()
  {
    // private constructor
  }
 
  @Override
  protected Object clone() throws CloneNotSupportedException
  {
    return instance;
  }
}
 
public class GFG
{
  public static void main(String[] args) throws CloneNotSupportedException
  {
    Singleton instance1 = Singleton.instance;
    Singleton instance2 = (Singleton) instance1.clone();
    System.out.println("instance1 hashCode:- "
                           + instance1.hashCode());
    System.out.println("instance2 hashCode:- "
                           + instance2.hashCode());
  }
}
Output:-
instance1 hashCode:- 366712642
instance2 hashCode:- 366712642
  1. 现在,由于两个实例的哈希码相同,这意味着它们代表单个实例。