单例模式
关于单例模式,可以戳这篇文章:《【设计模式】单例模式(Singleton Pattern)》
下面这是一个经典的懒汉式单例模式实现。
public class Singleton {
// 1.在类中添加一个私有静态成员变量用于保存唯一实例
private static Singleton instance;
// 2.将默认构造方法设置为私有,这样它就不能被new了
private Singleton() { }
// 3.写一个公有静态成员方法,暴露给外部用于获取唯一实例
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class SingletonTest {
public static void main(String[] args) throws Exception {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); // true
}
}
反射机制破解单例模式
单例模式的实现中有一个核心步骤,将无参构造方法设置为私有,防止被外部访问到,从而new
不出对象。
那么事情就变得非常简单——反射机制可以调用构造方法对象,且可以跳过访问权限检查,进行暴力反射。
public class DestroySingletonByReflect {
public static void main(String[] args) throws Exception {
// 加载类
Class.forName("Singleton");
// 通过反射获取无参构造方法对象
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
// 跳过权限检查,进行暴力反射
constructor.setAccessible(true);
// 通过无参构造方法对象获得实例
Singleton instance1 = constructor.newInstance();
Singleton instance2 = constructor.newInstance();
// 获取的还是同一个实例吗?
System.out.println(instance1 == instance2); // false
}
}
如何避免单例模式被反射机制破解?
也很好理解,反射机制破解单例模式的本质是对无参构造方法(对象)的调用。我们给无参构造方法也加上单例模式的逻辑即可。
public class Singleton {
private static Singleton instance;
private Singleton() {
// 给无参构造方法也加上单例模式的逻辑
if (instance == null) {
throw new RuntimeException();
}
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
序列化机制破解单例模式
序列化/序列化经常帮助我们曲线救国(必须Java对象的深拷贝),但它也有做坏事的时候。
想一想,如果我们把单例对象序列化后存进文件,然后反序列化出来,得到的还是原先的那个单例对象吗?
public class DestroySingletonBySerialize {
public static void main(String[] args) throws Exception {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); // true
// 序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("/Users/samarua/Documents/object.txt")));
objectOutputStream.writeObject(instance2);
// 反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("/Users/samarua/Documents/object.txt")));
Singleton instance3 = (Singleton) objectInputStream.readObject();
System.out.println(instance1 == instance3); // false
}
}
如何避免单例模式被序列化机制破解?
反序列化时永远不会调用构造方法,所以之前的反制方法失效了。这里,利用了序列化/反序列化机制的一个细节,在反序列化返回对象之前,会先看看有没有 readResolve()
方法,如果有,则返回该方法所返回的对象。
class Singleton implements Serializable {
private static Singleton instance;
private Singleton() { }
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// 反序列时通过该方法返回了instance实例
private Object readResolve() {
return instance;
}
}