灭霸所有单例模式,克隆、序列化、反射机制破坏7种单例模式_java


单例模式实际上也不止 7 种。但是,每一种都并非安全的。今天我给大家讲一讲如何利用克隆、序列化、反射机制破坏单例模式。

灭霸所有单例模式,克隆、序列化、反射机制破坏7种单例模式_java_02

我今天以痴汉式单例为例来讲,其他的单例模式破坏方式类似。

灭霸所有单例模式,克隆、序列化、反射机制破坏7种单例模式_java_03

上面这个单例实现,看似很完美。但我们通过克隆、序列化、反射机制,来击破这个单例模式。

创建一个 Java 对象一般有 4 种方式:new 、克隆、序列化、反射!现在 new 这种方式不能使用了,那我们还可以使用剩下的 3 种方式!

先来看克隆!

实现 Cloneable 接口,尽管构造函数是私有,但还会创建一个对象。因为 clone 方法不会调用构造函数,会直接从内存中 copy 内存区域。所以单例模式的类是切记不要实现 Cloneable 接口。

灭霸所有单例模式,克隆、序列化、反射机制破坏7种单例模式_java_04

自己运行一下,hash 值不一样,所以克隆成功了,生成了一个新对象。单例模式被成功破坏!

那么怎么抵制被克隆呢?

灭霸所有单例模式,克隆、序列化、反射机制破坏7种单例模式_java_05

就是重写 clone 方法,调用 getInstance() 方法,返回已有的实例即可!

现在我们再来看序列化是如何破坏单例模式的。现在假设你的单例模式,实现了 Serializable 接口。看我下面反序列化的案例!

灭霸所有单例模式,克隆、序列化、反射机制破坏7种单例模式_java_06

执行之后,hash 值不一样了,获取的对象非同一对象。结论,单例模式又被破坏了!那么怎么防止被反序列化呢?

很简单,自定义实现对象的 readResolve() 方法。

灭霸所有单例模式,克隆、序列化、反射机制破坏7种单例模式_java_07

为什么实现对象的 readResolve() 方法就可以了呢?这个你可以自己 debug 一下,上面反序列化的代码。其中有一个 readOrdinaryObject 方法在做怪!

灭霸所有单例模式,克隆、序列化、反射机制破坏7种单例模式_java_08

关键代码都注射的比较全,我相信你能看明白。如果还不明白,加我微信ID:xttblog。

最后,我们再来看反射是如何破坏单例模式的!

灭霸所有单例模式,克隆、序列化、反射机制破坏7种单例模式_java_09

执行之后,hash 值不一样了,获取的对象非同一对象。结论,单例模式又被破坏了!那么如何解决呢?很简单,加入下面的代码。

灭霸所有单例模式,克隆、序列化、反射机制破坏7种单例模式_java_10

因为执行反射会调用无参构造函数,所以上面的判断就可以起作用了!

综上所述,单例模式需要考虑,线程安全问题,效率问题,防止反射、反序列化、克隆。要不然,就有可能被***利用!

看到这里,有些人可能会问,这也太麻烦了,有没有更简便的方法呢?有,枚举模式。枚举类型是绝对单例的,可以无责任使用。

灭霸所有单例模式,克隆、序列化、反射机制破坏7种单例模式_java_11

一个枚举,就算实现双接口,也是无论如何都无法被破坏的。枚举无法克隆,没有这样的方法。没有构造函数,会抛出异常。就算你在枚举里加了构造函数,也是一样的。对于反序列化 Java 仅仅是将枚举对象的 name 属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的 valueOf 方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了 writeObject、readObject、readObjectNoData、writeReplace 和 readResolve 等方法。所以,枚举才是实现单例模式的最好方式!