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.ereadObject0
This 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_OBJECT
Of this typereadOrdinaryObject
Methods:If the class implements the serialization interface, newInstance is returned; otherwise, null is returned. If the class implements the serialization interface, pass is returnedreflection
Get 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 onehasReadResolveMethod
Method, 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 stepinvokeReadResolve
Methods:
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 thoseLanHanShi
orDouble lock
You 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).