单例模式与反射的博弈

单例模式与反射的博弈

1. 单例模式介绍

单例模式的核心概念是:私有化构造器,私有化静态对象属性,对外公开获取对象属性的方法,

从而使得外部类引用该类时,只存在唯一的一个对象。

2. 饿汉式单例模式代码

  • 饿汉式是最简单的一种实现方式,但是失去了 lazy loading (懒加载)的特性,被 final 和 static 同时修饰的属性会在类的准备阶段完成赋值
public class Singleton_1 {

    // 1. 私有化构造器
    private Singleton_1() {}

    // 2. 本类内部创建静态常量型实例
    private final static Singleton_1 instance = new Singleton_1();

    // 3. 对外提供公有的获取实例方法
    public static Singleton_1 getInstance() {
        return instance;
    }

}

3. 使用反射获取私有化构造器破解单例

        // 正常方式获得的对象
        Singleton_1 instance = Singleton_1.getInstance();
        // 获得class 对象
        Class singleton_class = instance.getClass();
        Constructor constructor = singleton_class.getDeclaredConstructor(); // 获取无参构造器
        constructor.setAccessible(true); // 给予私有构造器的使用权限
      
        // 使用构造器创建新的实例
        Singleton_1 singleton_1 = constructor.newInstance();
        System.out.println("创建新实例成功,破解成功...");
        System.out.println(instance.hashCode());
        System.out.println(singleton_1.hashCode());
        System.out.println(instance == singleton_1); // 对象比较
   
  • 输出结果如下:
反射破解单例.jpg

4. 单例模式在构造器中加入验证防止反射使用构造器

代码如下:

    // 防止反射破坏
    private static boolean flag = true;

    // 1. 私有化构造器
    private Singleton_1() {
        if (flag) {
            flag = false; // 在第一次构造完实例后将不能使用
        } else {
            throw new RuntimeException("单例模式遇到攻击,第二个对象未创建成功");
        }
    }

输出如下:


单例验证构造器.jpg

5. 反射修改flag验证

代码如下:

        Field flag = singleton_class.getDeclaredField("flag");
        flag.setAccessible(true);
        System.out.println(flag.get(instance));
        flag.set(instance, true); // 修改flag值为true
        System.out.println(flag.get(instance));
在修改完flag属性后,依旧能够破解单例模式。而Enum(枚举)独有的一些特性让反射不能够使用私有构造器去创建新的实例,因此推荐使用Enum来设计单例模式

6. Enum单例

  • 简洁好用
public enum SingletonEnum {
    INSTANCE;
    public void sayOK() {
        System.out.println("ok~");
    }
}
  • 测试代码
class Test_Enum {
    public static void main(String[] args) throws Exception{
        SingletonEnum instance_1 = SingletonEnum.INSTANCE;
        SingletonEnum instance_2 = SingletonEnum.INSTANCE;
        System.out.println("正常情况下,两个对象是否相同? " + (instance_1 == instance_2));

        // 使用反射
        Constructor constructor = SingletonEnum.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        SingletonEnum newInstance = constructor.newInstance();
        System.out.println("使用反射,能否创建不同实例?" + (instance_1 == newInstance));
    }
}
  • 输出结果
枚举单例测试.jpg
结果是直接报错,因为Enum(枚举)并没有无参构造器~ 不信就看下面代码
public abstract class Enum>
        implements Comparable, Serializable {

    private final String name;

    public final String name() {
        return name;
    }
    
    private final int ordinal;

    public final int ordinal() {
        return ordinal;
    }
    
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

定义枚举相当于自动继承了Enum类,而Enum类本身只有一个构造器,那就是 protected Enum(String name, int ordinal), 所以我们获取不到无参构造器。

那么用有参构造器会怎么样?
class Test_Enum {
    public static void main(String[] args) throws Exception{
        SingletonEnum instance_1 = SingletonEnum.INSTANCE;
        SingletonEnum instance_2 = SingletonEnum.INSTANCE;
        System.out.println("正常情况下,两个对象是否相同? " + (instance_1 == instance_2));
        // 拿enum定义的唯一的构造器
        Constructor constructor = SingletonEnum.class.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        SingletonEnum newInstance = constructor.newInstance("Test1", 11);
        System.out.println("使用反射,能否创建不同实例?" + (instance_1 == newInstance));
    }
}
  • 输出结果
反射枚举失败.jpg
通过第二行异常点进去看到源码
public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        ****省略****
        if ((clazz.getModifiers() & Modifier.ENUM) != 0) // 如果反射的类被Enum修饰,直接抛异常  
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ****省略****
 } 

你可能感兴趣的:(单例模式与反射的博弈)