这是我参与8月更文挑战的第30天,活动详情查看:8月更文挑战
内容接着上篇文章《挑战单例模式(六)》,上篇文章讲到了序列化会破坏单例,这篇文章来讲如何避免反射和序列化破坏单例实现。
九、注册式单例
上面说到,反射和序列化都会破坏单例的实现,注册式单例又被称为登记式单例,就是将每个实例都登记到某个地方,使用唯一的标识获取单例。注册单例有两种实现方式。
- 容器缓存
- 枚举登记
我们通过代码先实现枚举登记。
public enum SingletonInEnum {
INSTANCE;
private Object data;
public static SingletonInEnum getInstance() {
return INSTANCE;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
写法相当的简单,setData完成实例化对象的登记。
我们先用序列化去破坏这个单例。
public static void main(String[] args) {
SingletonInEnum instance1;
SingletonInEnum instance2 = SingletonInEnum.getInstance();
instance2.setData(new Object());
try(
FileOutputStream fos = new FileOutputStream("out.se");
ObjectOutputStream oos = new ObjectOutputStream(fos);
FileInputStream fis = new FileInputStream("out.se");
ObjectInputStream ois = new ObjectInputStream(fis);
){
oos.writeObject(instance2);
oos.flush();
instance1 = (SingletonInEnum)ois.readObject();
System.out.println(instance1.getData());
System.out.println(instance2.getData());
System.out.println(instance1.getData() == instance2.getData());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
运行结果如下。
结果显示,反序列化的实例对象跟原本的对象引用地址一致,是同一个对象。原理是什么呢?通过查看ObjectInputStream类的readEnum方法,我们发现了枚举类型其实是通过类名和Class对象找到一个唯一的枚举对象。因此,枚举对象是不可能被类加载器加载多次的。
序列化是不能破坏单例了,那么反射呢?
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
final Class<SingletonInEnum> singletonInEnumClass = SingletonInEnum.class;
final Constructor<SingletonInEnum> declaredConstructor = singletonInEnumClass.getDeclaredConstructor();
declaredConstructor.newInstance();
}
结果如下。
报的异常不是很常见,NoSuchMethodException,这个异常的含义是没有找到无参的构造方法。
通过查看enum源码,确实只有一个访问权限是protected的构造方法。
protected Enum(String name,int ordinal){
this.name = name;
this.ordinal = ordinal;
}
JVM语法规则是不允许通过反射来创建枚举类型,在反射中用到的newInstance方法,在这个方法中做了强制性的判断,如果修饰符是Modifier.ENUM枚举类型,直接抛出异常。 所以没可能用反射去创建枚举类型。
枚举单例因为枚举enum语法的特殊性,实现了特别巧妙的单例,它也是比较推崇的一种实现方式。
另外一种注册式单例就是用容器缓存去实现。原理不复杂,建议参考Spring容器。