前言
上一篇文章介绍了单例模式的几种写法,但是关于单例模式的问题还没有完全说完,今天我们继续介绍通过反射机制和序列化反序列化是如何破坏单例的以及解决方案,阅读本文前需要了解单例模式的几种写法,如果对单例模式的写法不清楚的同学可以参考:单例模式的六种写法
一.如何通过反射机制破坏单例模式
1. 什么是反射机制
JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。摘自: 百度百科
如果按照这种理解,我们之前的饿汉模式和懒汉模式为了不让其他外部类初始化本类实例,将构造方法私优化,对于反射机制到底有没有效果呢,也就是通过反射机制我们可以调用构造方法创建对象么?
2. 通过反射机制创建单例对象
package com.fatfat.sington.hungry.sington.reflection;
import com.fatfat.sington.hungry.sington.hungry.HungrySingleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @ClassName ReflectHungry
* @Auther LangGuofeng
* @Date: 2019/7/29/029 19:59
* @Description: 通过反射破坏饿汉模式的单例
*/
public class ReflectHungry {
public static void main(String[] args) {
try {
Class<?> clazz = HungrySingleton.class;
Constructor<?> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Object o1 = constructor.newInstance();
Object o2 = constructor.newInstance();
System.out.println(o1);
System.out.println(o2);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
运行结果,创建出了两个不同地址的实例。
分析:
- 首先,可以很明显的看出来是创建了两个不同的实例,打印的地址不同。
- 解决方式,暴力解决,在构造方法中判断实例是否为null,如果不为null,直接抛异常。
package com.fatfat.sington.hungry.sington.hungry;
/**
* @ClassName HungrySingtom
* @Auther LangGuofeng
* @Date: 2019/7/28/028 10:51
* @Description: 饿汉单例模式,正常new一个实例
*/
public class HungrySingleton {
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
private HungrySingleton() {
if(HUNGRY_SINGLETON != null){
throw new RuntimeException("此类被设计者设计成单例模式,不允许重复创建对象,请使用静态方法getInstance()获取实例对象。");
}
}
public static HungrySingleton getInstance() {
return HUNGRY_SINGLETON;
}
}
- 再次运行反射创建对象结果
- 反射创建对象也是通过构造方法,虽然反射可以执行私有的构造方法,但是我们也可以在私有的构造方法中加上逻辑判断提示通过反射创建对象的使用者,这个类被设计成单例模式,不允许创建多个对象,并给出获取对象的正确姿势。
二. 如何通过序列化和反序列化破坏单例模式
1.什么是序列化和反序列化
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
摘自: 百度百科
看重点: 以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
也就是说如果你的单例实现了Serializable,反序列化出来的对象,是重新创建的对象了。
2.通过反序列化创建
还是以饿汉模式单例为例子,想将饿汉单例模式实现序列化
package com.fatfat.sington.hungry.sington.hungry;
import java.io.Serializable;
/**
* @ClassName HungrySingtom
* @Auther LangGuofeng
* @Date: 2019/7/28/028 10:51
* @Description: 饿汉单例模式,正常new一个实例
*/
public class HungrySingleton implements Serializable {
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
private HungrySingleton() {
if(HUNGRY_SINGLETON != null){
throw new RuntimeException("此类被设计者设计成单例模式,不允许重复创建对象,请使用静态方法getInstance()获取实例对象。");
}
}
public static HungrySingleton getInstance() {
return HUNGRY_SINGLETON;
}
}
然后写序列化破坏测试
package com.fatfat.sington.hungry.sington.Serialization;
import com.fatfat.sington.hungry.sington.hungry.HungrySingleton;
import java.io.*;
/**
* @ClassName SerializationHungry
* @Auther LangGuofeng
* @Date: 2019/7/29/029 21:34
* @Description: 通过序列化和反序列化破坏饿汉模式的单例
*/
public class SerializationHungry {
public static void main(String[] args) {
try {
HungrySingleton hungrySingleton = HungrySingleton.getInstance();
System.out.println(hungrySingleton);
//将得到的实例序列化到磁盘
FileOutputStream fileOutputStream = new FileOutputStream("hungry.obj");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(hungrySingleton);
objectOutputStream.flush();
objectOutputStream.close();
//从磁盘反序列化得到实例
FileInputStream fileInputStream = new FileInputStream("hungry.obj");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
HungrySingleton hs = (HungrySingleton) objectInputStream.readObject();
System.out.println(hs);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果
分析:
- 两次打印实例地址不同,重新创建了对象,
- 解决方法,单例类中增加readResolve()方法,可以避免实例重复。
package com.fatfat.sington.hungry.sington.hungry;
import java.io.Serializable;
/**
* @ClassName HungrySingtom
* @Auther LangGuofeng
* @Date: 2019/7/28/028 10:51
* @Description: 饿汉单例模式,正常new一个实例
*/
public class HungrySingleton implements Serializable {
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
private HungrySingleton() {
if(HUNGRY_SINGLETON != null){
throw new RuntimeException("此类被设计者设计成单例模式,不允许重复创建对象,请使用静态方法getInstance()获取实例对象。");
}
}
public static HungrySingleton getInstance() {
return HUNGRY_SINGLETON;
}
private Object readResolve(){
return HUNGRY_SINGLETON;
}
}
- 再次运行结果
- 原因: 可以通过查看readObject()源码,看看是如何反序列化创建对象的,这个方法创建完对象之后会通过反射机制判断类中是否有readResolve()方法,如果有readResolve()方法,会通过反射机制调用这个方法。所以当你在单例类中写上readResolve()方法,是能够保证得到的同一个单例的,能够保证单例的全局唯一性。
补充: 但是避免不了序列化重复创建对象,实际上我们这种写法只是将反序列化创建的对象覆盖掉了,在执行过程中JVM还是创建了新的对象。