单例设计模式-枚举单例

枚举类天然的可序列化机制,可以强有力的保证不会出现多次实例化的情况,即使在复杂的序列化和反射攻击下,枚举类型的单例模式都没有问题。

代码

public enum EnumInstance {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumInstance getInstance() {
        return INSTANCE;
    }
}

序列化测试
首先测试枚举类型持有的INSTANCE

public class Test {

    static String FILE_NAME = "singleton_file";

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {

        EnumInstance instance = EnumInstance.getInstance();

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
        oos.writeObject(instance);

        File file = new File(FILE_NAME);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        EnumInstance newInstance = (EnumInstance) ois.readObject();

        System.out.println(newInstance == instance);
        System.out.println(newInstance);
        System.out.println(instance);

    }
}

结果

true
INSTANCE
INSTANCE

序列化测试
测试枚举类型持有的data

public class Test {

    static String FILE_NAME = "singleton_file";

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {

        EnumInstance instance = EnumInstance.getInstance();
        instance.setData(new Object());

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
        oos.writeObject(instance);

        File file = new File(FILE_NAME);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        EnumInstance newInstance = (EnumInstance) ois.readObject();

        System.out.println(newInstance.getData() == instance.getData());
        System.out.println(newInstance.getData());
        System.out.println(instance.getData());

    }
}

结果

true
java.lang.Object@7ef20235
java.lang.Object@7ef20235

源码
进入ObjectInputStream.readObject然后进入readObject0

进入readEnum方法

通过readString方法获取枚举的名称,声明Enum为null,在try/catch中赋值,再根据class和name获取枚举常量,因为枚举的名称是唯一的,且对应一个枚举常量,所以try/catch中拿到的enum就是唯一的常量,没有创建新的对象,维持了对象的单例属性。

反射测试

 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {

        Class objectClass = EnumInstance.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        // 把private方法设置为可访问
    }

结果

Exception in thread "main" java.lang.NoSuchMethodException: com.honor.design.pattern.creational.singleton.EnumInstance.()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at com.honor.design.pattern.creational.singleton.Test.main(Test.java:15)

报错:不能获取无参构造器

源码
进入java.lang.Enum,查看Enum构造方法,其中有且只有一个有参构造器。

修改测试类,使用有参构造器

  public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException{
        Class objectClass = EnumInstance.class;
        Constructor constructor = objectClass.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        EnumInstance enumInstance = (EnumInstance) constructor.newInstance("张三", 666);

        // 把private方法设置为可访问
    }

结果

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at com.honor.design.pattern.creational.singleton.Test.main(Test.java:17)

非法异常,不能反射创建枚举对象,异常出现再Constructor.java:417的newInstance,进入查看,可以看到方法中判断newInstance对象是不是枚举类型,所以对于枚举类型失败,无法进行发生攻击

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注