本文主要是介绍你的单例真的写对了吗?能经受住反射的考验吗,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
在我们日常的用法以及面试过程中,说到写单例,绝大部分都是采用二段锁写的,代码如下:
public class Singleton {private volatile static Singleton singleton;/*** 私有构造器*/private Singleton() {}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
下面来看一段调用:
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {Singleton singleton = Singleton.getInstance();System.out.println(singleton);Singleton reflectSingleton = (Singleton) Class.forName("org.white.whiteroot.Singleton").newInstance();System.out.println(reflectSingleton);System.out.println(singleton.equals(reflectSingleton));}
运行结果如下:
org.white.whiteroot.Singleton@4a574795
org.white.whiteroot.Singleton@f6f4d33
false
可以看到我们常自以为没问题的单例,很容易就被反射攻破了。
那么解决方案如何呢,我们都知道,反射其实还是会调用私有的无参数构造器,那么我们做如下改动如何(很多网上的结论都是下面这种方式):
// 新增属性
private static boolean constructed;
// 改写私有构造方法
private Singleton() {if (constructed) {throw new RuntimeException("Singleton has been constructed");}constructed = true;
}
此时再调用上面的测试方法,执行结果如下:
org.white.whiteroot.Singleton@4a574795
Exception in thread "main" java.lang.RuntimeException: Singleton has been constructed
可以看到通过反射构造失败了。
不过既然我们在用反射进行破坏,那么上面代码能经受住反射的考验吗?此处敲黑板。我们修改测试方法如下:
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {Singleton singleton = Singleton.getInstance();System.out.println(singleton);Class reflectSingletonClass = Class.forName("org.white.whiteroot.Singleton");Field field = reflectSingletonClass.getDeclaredField("constructed");field.setAccessible(true);field.set(singleton, false);Singleton reflectSingleton = (Singleton) reflectSingletonClass.newInstance();System.out.println(reflectSingleton);System.out.println(singleton.equals(reflectSingleton));}
再次运行,结果如下:
org.white.whiteroot.Singleton@4a574795
org.white.whiteroot.Singleton@23fc625e
false
我们发现,我们的单例再次被破坏了!
在我们日常编码中,很多事情都要动手去做,凡事多想一步。网上很多方案中都没有去尝试通过反射修改属性,这其实是一种不负责任的做法。
另外在overstackflow上有一种取巧的做法,不过也会存在反射的问题,其做法如下:
private Singleton() {if (singleton != null) {throw new RuntimeException("Singleton has been constructed");}}
这种做法依旧会存在一个问题,这个问题其实在以上所有方法中都存在:
Class reflectSingletonClass = Class.forName("org.white.whiteroot.Singleton");
Field field = reflectSingletonClass.getDeclaredField("singleton");
field.setAccessible(true);
field.set(singleton, null);
通过如上方法在反射中讲singleton的值置为空,这样相当于完全破换掉单例,每次置空后通过调用getInstance方法都会生成新的对象。
那么有没有不会被破坏的方式呢?答案当然是有的,从如下两个方案:
- 既然破坏单例都是通过反射破坏属性后调用无参构造器,那么我们可以把属性放到第三方去托管,比如把singleton放到redis托管,每次调用序列化回来,这样除非去攻击托管的地方,否则是改变不了咱们的单例的。
- 反射必定还是会调用构造器,那么咱们可以通过不调用构造器的方式来做单例对象,那就是枚举。
总结一句:目前来讲,通过枚举来防止反射是最优解决方案。
这篇关于你的单例真的写对了吗?能经受住反射的考验吗的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!