这是我参与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;
}
}
再次运行上面反射测试例子,结果如下。
确实解决了反射对单例的破坏,但是代码逻辑冗长,不够优雅!如何解决呢?答案是使用注册式单例。