关于老师讲到的单例模式的一些问题

来源:5-6 无视反射和序列化攻击的单例

qq_慕前端3164363

2020-04-04

我看另一个提问当中,老师回答说,加装了枚举的饿汉模式中,真正的单例是指内部枚举持有的单例,在后面的验证代码中也有所体现

System.out.println(EnumStarvingSingleton.getInstance());
Class clazz = EnumStarvingSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
EnumStarvingSingleton enumStarvingSingleton = (EnumStarvingSingleton)constructor.newInstance();
System.out.println(enumStarvingSingleton.getInstance());

无论是EnumStarvingSingleton本身,还是通过反射创建enumStarvingSingleton实例,都是通过调用getInstance()方法来获取枚举中持有的实例的。

但是我也就产生了针对上一节课中普通饿汉模式的一些疑问。在上一节课中,对于普通饿汉模式的单例遭到破坏的演示代码如下:

System.out.println(StarvingSingleton.getInstance());
Class clazz = StarvingSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
System.out.println(constructor.newInstance());

显然输出的两个实例是不同的,因为第一个实例是由StarvingSingleton类中的成员变量instance所指向的一个实例,第二个实例是通过反射创建出来的新对象。
但是我发现,如果再次调用通过反射创建出来的新对象的getInstance()方法,同样可以保证单例“不被破坏”,只需要将上面代码中的

System.out.println(constructor.newInstance());

修改为

System.out.println(constructor.newInstance().getInstance());

即可,这样子输出的两个实例就有是同一个对象了,我也简单梳理了一下内存中的实际情况
图片描述
第一次单例遭到破坏的代码中,对比的是实例A和实例B,显然不是同一个对象,但是如果实例B调用也getInstace方法,返回的又是实例A,两者就又会相同。

所以这样子是不是和加装了枚举的饿汉模式会有一些相似性?

  • 普通的饿汉模式中,真正的单例是成员变量instance持有的单例
  • 加装了枚举的饿汉模式中,真正的单例是指内部枚举持有的单例

普通的饿汉模式中,我可以调用getInstance()方法(类似于加装了枚举的饿汉模式)得到唯一的实例,虽然它的外层依然无法防御反射的攻击,但是内部的成员变量instance是无法修改的(类似于加装了枚举的饿汉模式中的内部枚举),这样子看来普通的饿汉模式似乎也是可以“无视反射”的了?

写回答

3回答

orzzzz

2020-04-14

Class enumClass = Class.forName("org.simpleframework.core.BeanContainer$ContainerHolder");
Field holderField = enumClass.getField("HOLDER");
holderField.setAccessible(true);
Object enumValue = holderField.get(null);
Field enumField = enumClass.getDeclaredField("instance");
enumField.setAccessible(true);
enumField.set(enumValue, null);
0
0

翔仔

2020-04-06

同学好,太棒了,还配合了自己的画图:)针对同学的问题,对于普通饿汉模式来讲,如果我通过反射去获取到类里面的这个私有成员变量instance,然后替换掉它的值,之后别人通过getInstance方法获取的时候值也会改变呢

0
4
翔仔
回复
慕沐4323715
同学好,能修改instance这个变量,但是修改不了它对应的那个枚举类型的单例
2020-12-06
共4条回复

orzzzz

2020-04-05

我看的时候也有类似的疑问,后面在网上看了一下,发现他们是直接用枚举实现的,感觉这样是可以无视反射的

public enum Singleton {
    INSTANCE;    
    public void doSomething() {
        System.out.println("doSomething");
    }
}
0
0

剑指Java自研框架,决胜Spring源码

快速入门Spring核心源码+从零开发自研框架

1499 学习 · 495 问题

查看课程