We all know that reflection and serialization can break singletons, but some of us may not know how to prevent this. Here is how to prevent singletons from being broken by reflection and serialization.

1. Singleton mode

Singleton: As the name implies, there is only one instance and it is responsible for creating its own objects. This class provides a way to access its unique objects directly, without instantiating the objects of that class.

Implementation: singleton mode implementation of many ways, such as lazy, hungry, double check lock, static internal class, enumeration and so on, here is not a paste out code to see, unfamiliar or interested students can click the back link to view their own. The singleton pattern

We first write a hungry here, convenient for the use of the following article.

public class HungrySingleton implements Serializable {

    private final static HungrySingleton hungrySingleton;

    / / initialization
 static {  hungrySingleton = new HungrySingleton();  }   // Privatize the constructor  private HungrySingleton(a){   }   // provide unique access  public static HungrySingleton getInstance(a){  return hungrySingleton;  } } Copy the code

Serialization breaks the singleton pattern

GetInstance () gets the object, serializes it to a file, reads the file to get a new object, and checks whether the two objects are the same.

public class HungrySingletonTest {

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

        // Get the object
 HungrySingleton instance = HungrySingleton.getInstance();  // Serialize the object to a file  ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("hungrysingleton.txt"));  objectOutputStream.writeObject(instance);   // Read the file  File file = new File("hungrysingleton.txt");  ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));  HungrySingleton newInstance = (HungrySingleton) objectInputStream.readObject();   // Check whether it is the same object  System.out.println("Initial object:" + instance);  System.out.println("Deserialized object:" + newInstance);   System.out.println(instance == newInstance);  } } Copy the code

It is obvious that serialization breaks the singleton pattern by deserializing different objects.

The reason:Since deserialization reads data from a file, take a look at ObjectInputStream’s readObject() method, which focuses on the point of interruption below, i.ereadObject0This method:The code inside this method is quite long, I won’t take a screenshot, this method inside there is a switch judgment, determine the read type, because just read the Object type, so take a lookTC_OBJECTOf this typereadOrdinaryObjectMethods:If the class implements the serialization interface, newInstance is returned; otherwise, null is returned. If the class implements the serialization interface, pass is returnedreflectionGet a new instance, so deserialize get a different object!

3. Reflection destruction singleton mode

Get the constructor of the HungrySingleton class, then change the permission to true, then newInstance gets a newInstance, check whether the two instances are the same, the code is as follows:

public class HungrySingletonTest {

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Class hungrySingletonClass = HungrySingleton.class;
 // Get the HungrySingleton class constructor  Constructor constructor = hungrySingletonClass.getDeclaredConstructor();  // Change the constructor permission to true  constructor.setAccessible(true);  HungrySingleton instance = HungrySingleton.getInstance();  HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();   System.out.println("Initial instance:" + instance);  System.out.println("Example of reflection:" + newInstance);  System.out.println(instance == newInstance);  } } Copy the code

As you can see, the resulting instances are different, indicating that the singleton pattern is broken by reflection!

The reason: because reflection changes the private property of the constructor, the class can get a new instance from the constructor, which must be different from the initialized instance.

Serialization destruction solution

To resolve the serial-breaking singleton pattern, it’s easy to add a method called readResolve() to the singleton class:

public class HungrySingleton implements Serializable {

    private final static HungrySingleton hungrySingleton;

    / / initialization
 static {  hungrySingleton = new HungrySingleton();  }   // Privatize the constructor  private HungrySingleton(a){   }   // provide unique access  public static HungrySingleton getInstance(a){  return hungrySingleton;  }   // Return a unique object  private Object readResolve(a){  return hungrySingleton;  } } Copy the code

Execute the main method again and see the following result: You can see that the same object is returned. For the reason, read on.

When deserializing the source code above, it goes to the following image:Since the class implements the serialization interface, then return a new instance, then obj must not be null.And you can see it calls onehasReadResolveMethodMethod, this method you go to look at the comment:If the class you want to deserialize has a readResolve method, it will return true and proceed to the next stepinvokeReadResolveMethods:

invokeReadResolve(Object obj)

The readResolve method returns a unique instance of the HungrySingleton class, and the resulting object is the same as that obtained by getInstance()!

5. Reflection destroys the solution

Reflection destruction solution, for the realization of the different way, such as reading the singleton pattern is hungry, is the time when the class is initialized object has been created, for this, we can modify the code, within the constructor to increase:

public class HungrySingleton implements Serializable {

    private final static HungrySingleton hungrySingleton;

    / / initialization
 static {  hungrySingleton = new HungrySingleton();  }   // Privatize the constructor  private HungrySingleton(a){  if(hungrySingleton ! =null) { throw new RuntimeException("Singleton constructor disallows reflection calls");  }  }   // provide unique access  public static HungrySingleton getInstance(a){  return hungrySingleton;  } } Copy the code

If it’s one of thoseLanHanShiorDouble lockYou can add a static variable, then change the value of the static variable at class initialization, and then check the value of the static variable in the constructor to do the corresponding operation!

6. Public account

If you think my article is helpful to you, please pay attention to my wechat official account :” A happy and painful programmer “(no advertising, simply share original articles, pJ practical tools, a variety of Java learning resources, looking forward to common progress with you).