theme: channing-cyan
这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战
内容接着上篇文章《挑战单例模式(四)》,上篇文章讲到了静态内部类单例实现方式,这篇文章不讲实现,我们理性分析地我们实现的所有的单例是否真的就高枕无忧了,这篇讲反射对单例的破坏,以及解决方案。
七、反射破坏单例
在上面实现的单例所有方式中,虽然从正常情况下,因为构造方法是私有的,对象实例的流程通过不同的方式保证只会进行一次。但凡事都不要太绝对啦,有一种特殊的利器可以避开正常的对象构造方法,那就是反射。
Java反射是什么,它是一组API,提供了用于在运行时检查或修改方法、类或接口的方法。反射为我们提供了关于对象所属类的信息,以及可以通过使用对象执行的类的方法;通过反射,我们可以在运行时调用方法,而不用考虑方法的访问权限。所以总的来说,我们可以通过反射获取对象所属的类的名称、对象的构造函数、方法及其方法描述(注释等) 。
我们借用第三章实现的懒汉式单例模式SingletonInLazy
类,使用反射实例化对象。
public class ReflectDestroysSingleton { public static void main(String[] args) { //好事者干的好事 final Class<SingletonInLazy> clazz = SingletonInLazy.class; //通过反射获取私有构造方法 final Constructor<SingletonInLazy> declaredConstructor; try { declaredConstructor = clazz.getDeclaredConstructor(null); //强制性访问 declaredConstructor.setAccessible(true); //进行两次实例化 final SingletonInLazy instance1 = declaredConstructor.newInstance(); final SingletonInLazy instance2 = declaredConstructor.newInstance(); System.out.println(instance1 == instance2); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
运行结果为false
。很显然,这个例子我们通过反射技术创建了两个不同的实例。通过这种方式,不可访问的私有构造函数变得可访问,并且使类成为单例的的目标被破坏。那么怎么来挽救一下呢?
方法很简单,在私有构造方法里面抛异常
public class UpgradeSingletonInLazy { private UpgradeSingletonInLazy() { if(INSTANCE != null){ throw new AssertionError(); } System.out.println("----------私有构造方法实例化完成---------"); INSTANCE = this; } public static UpgradeSingletonInLazy INSTANCE = null; static final Runnable RUNNABLE = () -> { final UpgradeSingletonInLazy instance = UpgradeSingletonInLazy.getInstance(); System.out.println(Thread.currentThread().getName() + ":" + instance); }; public static UpgradeSingletonInLazy getInstance() { if (INSTANCE == null) { INSTANCE = new UpgradeSingletonInLazy(); } return INSTANCE; } }
再次运行上面反射测试例子,结果如下。
确实解决了反射对单例的破坏,但是代码逻辑冗长,不够优雅!如何解决呢?答案是使用注册式单例。