前几天面试的时候被问到了这样一个问题 “单例模式能用反射得到实例吗?”,回答的不是很好,借此机会记录一下。
首先先针对这个问题回答一下,可以通过反射得到实例。其实原理很简单,就是通过反射破坏私有的构造方法。通过setAccessible(true),然后得到newInstance实例。因为不管是懒汉、饿汉还是内部类实现的单例模式,总会有私有的构造方法。只要有私有的无参构造方法,就能通过反射得到实例。
首先要知道什么是单例模式、单例模式的好处在哪?
Java Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。
使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。
常规单例模式的实现(懒汉模式)
public class SingletonDemo {
private static SingletonDemo singleton;
private SingletonDemo(){}
public static SingletonDemo getInstance() {
if (singleton == null) {
singleton = new SingletonDemo();
}
return singleton;
}
}
利用反射破坏单例模式
class SingletonReflectionTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException {
SingletonDemo s1 = SingletonDemo.getInstance();
Constructor<SingletonDemo> constructor = SingletonDemo.class.getDeclaredConstructor();
constructor.setAccessible(true);
SingletonDemo s2 = constructor.newInstance();
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}
2084435065
1896277646
输出结果显示这两个不是同一个对象。 虽然不是同一个对象,s2对象是通过反射得到的实例,只不过单例模式失去了应有的意义,被破坏了而已。
失败的【防止单例模式被破坏】
public class SingletonDemo {
private static SingletonDemo singleton;
private static int count;
private SingletonDemo(){
synchronized (SingletonDemo.class) {
if (count>0) {
throw new RuntimeException("error:创建了两个实例!");
}
++count;
}
}
public static SingletonDemo getInstance() {
if (singleton == null) {
singleton = new SingletonDemo();
}
return singleton;
}
}
划重点!!!
上面这种通过count的方式防止不了反射攻击。可以通过反射修改count的属性值,具体实现如下:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Constructor<SimpleSingleton> declaredConstructor = SimpleSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
// 反射获取count变量
Field countField = SimpleSingleton.class.getDeclaredField("count");
countField.setAccessible(true);
// new了一次构造方法,count=1
SimpleSingleton instance = SimpleSingleton.getInstance();
// 重新设置count的值为0
countField.set(instance, 0);
// newInstance底层原理也是调用构造方法来进行对象的实例化的
// count=1
SimpleSingleton simpleSingleton = declaredConstructor.newInstance();
simpleSingleton.say();
System.out.println(instance == simpleSingleton);
}
正确的防止单例模式被破坏
要想防止反射破坏,可以使用枚举单例来实现。
public enum EnumSingleton {
INSTANCE;
public void say() {
System.out.println("枚举单例");
}
}