反射如何破坏单例模式
一个单例类:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
通过反射破坏单例模式:
public class Solution {
public static void main(String[] args) throws Exception{
// 第一个实例
Singleton s1 = Singleton.getInstance();
// 通过反射得到构造器
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
// 第二个实例
Singleton s2 = constructor.newInstance();
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}
输出结果:
5548145
3655388
结果表明s1和s2是两个不同的实例了。
通过反射获得单例类的构造函数,由于该构造函数是private的,通过setAccessible(true)指示反射的对象在使用时应该取消 Java 语言访问检查,使得私有的构造函数能够被访问,这样使得单例模式失效。
解决方案
如果要抵御这种攻击,要防止构造函数被成功调用两次。需要在构造函数中对实例化次数进行统计,大于一次就抛出异常。
public class Singleton {
// 统计次数
private static int count = 0;
private static Singleton instance = null;
// 在构造方法中进行次数统计与判断
private Singleton(){
// 这里加锁,防止同步操作
synchronized (Singleton.class) {
if(count > 0){
throw new RuntimeException("创建了两个实例");
}
count++;
}
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) throws Exception {
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton s1 = constructor.newInstance();
Singleton s2 = constructor.newInstance();
}
}
执行结果:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at leetcode.Solution.main(Solution.java:38)
Caused by: java.lang.RuntimeException: 创建了两个实例
at leetcode.Solution.<init>(Solution.java:19)
... 5 more