单例设计模式-枚举单例
最后更新于:2022-08-15 13:32:41
枚举类天然的可序列化机制,可以强有力的保证不会出现多次实例化的情况,即使在复杂的序列化和反射攻击下,枚举类型的单例模式都没有问题。
代码
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对象是不是枚举类型,所以对于枚举类型失败,无法进行发生攻击