设计模式 | 挑战单例模式(七)

本文探讨了如何防止反射和序列化破坏单例模式,重点介绍了注册式单例的两种实现:枚举登记和容器缓存。枚举单例由于其特殊性,能有效抵御序列化和反射的攻击,是实现单例的一种推荐方式。同时,文章通过代码示例展示了枚举单例在序列化和反射下的表现,证明了其单例的稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


theme: channing-cyan

这是我参与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();       } ​   }

运行结果如下。

reg1.png

结果显示,反序列化的实例对象跟原本的对象引用地址一致,是同一个对象。原理是什么呢?通过查看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(); }

结果如下。

reg2.png

报的异常不是很常见,NoSuchMethodException,这个异常的含义是没有找到无参的构造方法。

通过查看enum源码,确实只有一个访问权限是protected的构造方法。

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

JVM语法规则是不允许通过反射来创建枚举类型,在反射中用到的newInstance方法,在这个方法中做了强制性的判断,如果修饰符是Modifier.ENUM枚举类型,直接抛出异常。 所以没可能用反射去创建枚举类型。

枚举单例因为枚举enum语法的特殊性,实现了特别巧妙的单例,它也是比较推崇的一种实现方式。

另外一种注册式单例就是用容器缓存去实现。原理不复杂,建议参考Spring容器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值