Thursday, 25 July 2019

How to Prevent the Singleton Design Pattern from Reflection, Deserialization, and Cloning

        In earlier posts, we discussed how to implement the Singleton Design Pattern and the different approaches to creating a singleton class in Java. As we know, the Singleton Design Pattern ensures that only one instance of a class is created and provides a global point of access to that instance throughout the application. However, in certain scenarios, the singleton property can be broken.

There are three main ways in which the singleton property of a class can be violated. In this post, we will discuss how each of these scenarios can break the singleton pattern and how to prevent them.


  • Reflection 
        Using the Reflection API, it is possible to create multiple instances of a singleton class, thereby breaking the Singleton Design Pattern.

Consider the following example, which demonstrates how reflection can break the singleton pattern:

Singleton.java,

package com.example.demo;

public class Singleton {
 
        private static Singleton instance = null;
 
        private Singleton() {
  
        }
 
        public static Singleton getInstance() {
                if (instance == null) {
                         synchronized (Singleton.class) {
                                    if (instance == null) {
                                            instance = new Singleton();
                                    }
                         }
                 }
                 return instance;
         }
}

Using the Reflection API, we can break the Singleton Design Pattern, as shown in the following code.

package com.example.demo;

import java.lang.reflect.Constructor;

public class ReflectionSingleton {
 
         public static void main(String[] args) {
                   
                  Singleton objOne = Singleton.getInstance();
                  Singleton objTwo = null;
                  try {
                         Constructor constructor = Singleton.class.getDeclaredConstructor();
                         constructor.setAccessible(true);
                         objTwo = (Singleton) constructor.newInstance();
                   } catch (Exception ex) {
                            System.out.println(ex);
                   }
  
                   System.out.println("Hashcode of Object 1 - "+objOne.hashCode());
                   System.out.println("Hashcode of Object 2 - "+objTwo.hashCode());
         }

}

Output:-
Hashcode of Object 1 - 366712642
Hashcode of Object 2 - 1829164700


The two singleton instances have different hash codes, which means the Singleton Design Pattern has been broken.

How to Prevent Singleton pattern from Reflection:-

         There are several ways to prevent the Singleton Design Pattern from being broken by the Reflection API. One of the best approaches is to throw a RuntimeException from the constructor if an instance of the class already exists. This prevents the creation of a second instance, even through reflection.

Code:-
private Singleton() throws Exception {
        if (instance != null) {
                   throw new RuntimeException("Instance already exists");
        }
}


  • Deserialization
        Serialization allows us to convert an object into a byte stream so that it can be stored in a file or transmitted over a network. If a singleton object is serialized and then deserialized, the deserialization process creates a new instance of the class. As a result, the Singleton Design Pattern is broken.

The following example demonstrates how serialization and deserialization can break the Singleton Design Pattern:

package com.example.demo;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class DeserializationSingleton {
 
         public static void main(String[] args) throws Exception {
  
                   Singleton instanceOne = Singleton.getInstance(); 
                   ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file.text")); 
                   out.writeObject(instanceOne); 
                   out.close(); 
  
                   ObjectInput in = new ObjectInputStream(new FileInputStream("file.text")); 
                   Singleton instanceTwo = (Singleton) in.readObject(); 
                   in.close(); 
  
                    System.out.println("hashCode of instanceOne is - " + instanceOne.hashCode()); 
                    System.out.println("hashCode of instanceTwo is - " + instanceTwo.hashCode()); 
          }  

}

Output:-
hashCode of instanceOne is - 1550089733
hashCode of instanceTwo is - 2003749087


As shown in the output above, two different objects are created because the hash codes of instanceOne and instanceTwo are different. Therefore, the Singleton Design Pattern is broken.


Prevent Singleton Pattern from Deserialization:-

As shown in the output above, two different objects have been created because the hash codes of instanceOne and instanceTwo are different. Therefore, the Singleton Design Pattern has been broken.

Singleton.java,

   @override
     protected Object readResolve() { 
           return instance; 
     } 

Now run the DeserializationDemo class and observe the output:

hashCode of instanceOne is - 1550089733
hashCode of instanceTwo is - 1550089733

  • Cloning
        Using the clone() method, we can create a copy of an existing object. Similarly, if cloning is applied to a singleton class, it creates a second instance in addition to the original object. As a result, the Singleton Design Pattern is broken, as shown in the following example.

To demonstrate this, implement the Cloneable interface and override the clone() method in the singleton class.

Singleton.java
  .....
  @Override
  protected Object clone() throws CloneNotSupportedException  { 
          return super.clone(); 
  } 
....

Main Class,
package com.example.demo;

public class CloningSingleton {
 
        public static void main(String[] args) throws CloneNotSupportedException, Exception {
                 Singleton instanceOne = Singleton.getInstance();
                 Singleton instanceTwo = (Singleton) instanceOne.clone();
                 System.out.println("hashCode of instance1 - "+instanceOne.hashCode());
                 System.out.println("hashCode of instance2 - "+instanceTwo.hashCode());
        }

}

Output:-
hashCode of instance1 - 366712642
hashCode of instance2 - 1829164700


As shown in the output above, the two instances have different hash codes, which means they are different objects. Therefore, the Singleton Design Pattern has been broken.


Prevent Singleton Pattern from Cloning:-

In the above code, the Singleton Design Pattern is broken because two instances of the singleton class are created. To prevent this, override the clone() method and throw a CloneNotSupportedException. If anyone attempts to create a clone of the singleton object, the clone() method will throw this exception, as shown in the following code.

Singleton.java
  ......
  @Override
  protected Object clone() throws CloneNotSupportedException  { 
           throw new CloneNotSupportedException(); 
  } 
........

Now run the main class. When it attempts to create a clone of the singleton object, a CloneNotSupportedException will be thrown.

Thank you for visiting the blog!


Related Posts:--
1) Singleton Design Pattern in java with example
2) Factory Design Pattern in Java
3) Builder Design Pattern in Java

No comments:

Post a Comment