如何防止单例模式被 JAVA 反射攻击
package sf.com.singleton;
public class Demo {
private static boolean flag = true;
private Demo() {
System.out.println("flag==" + flag);
}
private static class SingletonHolder{
private static final Demo INSTANCE = new Demo();
}
public static Demo getInstance(){
return SingletonHolder.INSTANCE;
}
public void deSomethingElse(){
}
}
package sf.com.singleton;
import java.lang.reflect.Constructor;
public class DemoRelectAttack {
public static void main(String[] args){
try {
Class<Demo> classType = Demo.class;
Constructor<Demo> constructor = classType.getDeclaredConstructor(null);
//取消java的权限控制检查
constructor.setAccessible(true);
//下面两个都可以访问私有构造器
Demo demo1 = (Demo) constructor.newInstance();
Demo demo2 = Demo.getInstance();
System.out.println(demo1 == demo2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
可以看到,通过反射获取构造函数,然后调用setAccessible(true)就可以调用私有的构造函数,所有e1和e2是两个不同的对象。
如果要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
修改后的代码:
package sf.com.singleton;
public class DemoModify {
private static boolean flag = true;
private DemoModify() {
synchronized (DemoModify.class) {
if (flag == false) {
flag = !flag;
}else{
throw new RuntimeException("单例模式被侵犯!");
}
}
}
private static class SingletonHolder{
private static final DemoModify INSTANCE = new DemoModify();
}
public static DemoModify getInstance(){
return SingletonHolder.INSTANCE;
}
public void deSomethingElse(){
}
}
package sf.com.singleton;
import java.lang.reflect.Constructor;
public class DemoRelectAttack {
public static void main(String[] args){
try {
Class<DemoModify> classType = DemoModify.class;
Constructor<DemoModify> constructor = classType.getDeclaredConstructor(null);
//取消java的权限控制检查
constructor.setAccessible(true);
//下面两个都可以访问私有构造器
DemoModify demo1 = (DemoModify) constructor.newInstance();
DemoModify demo2 = DemoModify.getInstance();
System.out.println(demo1 == demo2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果如下:
可以看到,成功的阻止了单例模式被破坏。
从JDK1.5开始,实现Singleton还有新的写法,只需编写一个包含单个元素的枚举类型。推荐写法:
package sf.com.singleton;
public enum SingletonClass {
INSTANCE;
public void test(){
System.out.println("The Test!");
}
}
package sf.com.singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class TestMain {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class<SingletonClass> classType = SingletonClass.class;
Constructor<SingletonClass> constructor =(Constructor<SingletonClass>) classType.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
}
}
运行结果如下:
由此可见这种写法也可以防止单例模式被“攻击”。
2、第二种方法简单直接
private Singleton() {
if(singleton!=null){
throw new RuntimeException();
}
}
public class Demo {
public static void main(String[] args) throws Exception {
Singleton singletonOne = Singleton.getSingleton();
Singleton singletonTwo = Singleton.getSingleton();
System.out.println("One和Two是否是同一个实例? " + (singletonOne == singletonTwo));
//利用反射来破坏单例模式
Class clazz = Class.forName("com.jiagouedu.Singleton");
Constructor constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton singletonThree = (Singleton) constructor.newInstance(null);
System.out.println("One和three是否是同一个实例? "+(singletonOne==singletonThree));
}
}
//双重检测锁模式
class Singleton {
private Singleton() {
if(singleton!=null){
throw new RuntimeException();
}
}
static Singleton singleton;
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}