如何防止单例模式被 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(); } }
运行结果如下:
Exception in thread "main" java.lang.NoSuchMethodException: sf.com.singleton.SingletonClass.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at sf.com.singleton.TestMain.main(TestMain.java:10)
由此可见这种写法也可以防止单例模式被“攻击”。
不积跬步无以至千里不积小流无以成江海