Hello everyone;
(source code : https://github.com/serdaralkancode/cracksingletonpattern)
How to brake singleton pattern and how to prevent ways. In this article, I want to show you these ways.
There are 3 ways.
- Brake via Reflection API
public class MyObject {
private MyObject() {};
public static final MyObject instance = new MyObject();
}
public class ReflectionTest {
public static void main(String[] args) {
MyObject ins1 = MyObject.instance;
MyObject ins2 = null;
Constructor[] constructors = MyObject.class.getDeclaredConstructors();
for(Constructor constructor : constructors)
{
constructor.setAccessible(true);
try
{
ins2 = (MyObject) constructor.newInstance();
}
catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
//different hashcode
System.out.println("ins1 hashcode :" + ins1.hashCode());
System.out.println("ins2 hashcode :" + ins2.hashCode());
}
}
different hashcode on output
ins1 hashcode :1705736037
ins2 hashcode :558638686
- Prevent Singleton from Reflection
Solution : throw an exception from private constructor
public class MyObject2 {
private MyObject2() {
if(MyObject2.instance != null)
{
//add this exception
try {
throw new InstantiationException("creating is not allowed");
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
}
};
public static final MyObject2 instance = new MyObject2();
}
public class ReflectionTest2 {
public static void main(String[] args) {
MyObject2 ins1 = MyObject2.instance;
MyObject2 ins2 = null;
Constructor[] constructors = MyObject2.class.getDeclaredConstructors();
for(Constructor constructor : constructors)
{
constructor.setAccessible(true);
try
{
ins2 = (MyObject2) constructor.newInstance();
}
catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
System.out.println("ins1 hashcode :" + ins1.hashCode());
System.out.println("ins2 hashcode :" + ins2.hashCode());
}
}
output is :
Exception in thread "main" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at tech1.prevent.ReflectionTest2.main(ReflectionTest2.java:25)
Caused by: java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
at tech1.prevent.ReflectionTest2.main(ReflectionTest2.java:22)
Caused by: java.lang.RuntimeException: java.lang.InstantiationException: creating is not allowed
at tech1.prevent.MyObject2.<init>(MyObject2.java:13)
... 6 more
Caused by: java.lang.InstantiationException: creating is not allowed
at tech1.prevent.MyObject2.<init>(MyObject2.java:11)
... 6 more
2. Brake via Serialization and DeSerialization
public class MySerializableObject implements Serializable {
private static final long serialVersionUID = -1110l;
private MySerializableObject() {};
public static final MySerializableObject instance = new MySerializableObject();
}
public class SerializeDeserializeTest {
public static void main(String[] args) {
MySerializableObject ins1 = MySerializableObject.instance;
MySerializableObject ins2 = null;
String file = "C://temp/myObject.ser";
try
{
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream outputStream = new ObjectOutputStream(fos);
outputStream.writeObject(ins1);
fos.close();
outputStream.close();
FileInputStream fis = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fis);
ins2 = (MySerializableObject) objectInputStream.readObject();
fis.close();
objectInputStream.close();
} catch (IOException | ClassNotFoundException e) {
System.out.println("error" + e.getMessage());
}
//diffrent hashcode
System.out.println("ins1 hashcode " + ins1.hashCode());
System.out.println("ins2 hashcode " + ins2.hashCode());
}
}
different hashcode on output:
ins1 hashcode 883049899
ins2 hashcode 396180261
- Prevent Singleton from Serialization and Deserialization
Solution : override readResolve method
public class MySerializableObject2 implements Serializable {
private static final long serialVersionUID = -11102;
private MySerializableObject2() {};
public static final MySerializableObject2 instance = new MySerializableObject2();
protected Object readResolve()
{
System.out.println("readResolve method is called");
return instance;
}
}
public class SerializeDeserializeTest2 {
public static void main(String[] args) {
MySerializableObject2 ins1 = MySerializableObject2.instance;
MySerializableObject2 ins2 = null;
String file = "C://temp/myObject2.ser";
try
{
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream outputStream = new ObjectOutputStream(fos);
outputStream.writeObject(ins1);
fos.close();
outputStream.close();
FileInputStream fis = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fis);
ins2 = (MySerializableObject2) objectInputStream.readObject();
fis.close();
objectInputStream.close();
} catch (IOException | ClassNotFoundException e) {
System.out.println("error" + e.getMessage());
}
//same hashcode
System.out.println("ins1 hashcode " + ins1.hashCode());
System.out.println("ins2 hashcode " + ins2.hashCode());
}
}
output is :
readResolve method is called
ins1 hashcode 883049899
ins2 hashcode 883049899
3. Brake via Clonable
public class MyClonableParentObject implements Cloneable {
int val = 10;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class MySingletonObject extends MyClonableParentObject{
public static final MySingletonObject instance = new MySingletonObject();
private MySingletonObject() {};
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class ClonableTest {
public static void main(String[] args) throws CloneNotSupportedException {
MySingletonObject ins1 = MySingletonObject.instance;
MySingletonObject ins2 = (MySingletonObject) ins1.clone();
//different hashcode
System.out.println("ins1 hashcode " + ins1.hashCode());
System.out.println("ins2 hashcode " + ins2.hashCode());
}
}
different hashcode on output:
ins1 hashcode 455659002
ins2 hashcode 1149319664
- Prevent Singleton from Clonable
Solution : throw excepiton or return same instance on overriding clone method
public class MySingletonObject2 extends MyClonableParentObject {
public static final MySingletonObject2 instance = new MySingletonObject2();
private MySingletonObject2() {};
@Override
protected Object clone() throws CloneNotSupportedException {
return instance; //or throw exception
}
}
public class ClonableTest2 {
public static void main(String[] args) throws CloneNotSupportedException {
MySingletonObject2 ins1 = MySingletonObject2.instance;
MySingletonObject2 ins2 = (MySingletonObject2) ins1.clone();
//same hashcode
System.out.println("ins1 hashcode " + ins1.hashCode());
System.out.println("ins2 hashcode " + ins2.hashCode());
}
}
output is :
ins1 hashcode 455659002
ins2 hashcode 455659002
I hope this was a useful article for you.