面试官看完我手写的单例直接惊呆了! (2)

可以发现,我把单例对象序列化之后,再反序列化之后得到的对象,和之前已经不是同一个对象了。因此,就破坏了单例。

那怎么解决这个问题呢?

我先说解决方案,一会儿解释为什么这样做可以。

很简单,在单例类中添加一个方法 readResolve 就可以了,方法体中让它返回我们创建的单例对象。

面试官看完我手写的单例直接惊呆了!

然后再次运行测试类会发现,打印出来的 hashCode 码一样。

是不是很神奇。。。

面试官看完我手写的单例直接惊呆了!

readResolve 为什么可以解决序列化破坏单例的问题?

我们通过查看源码中一些关键的步骤,就可以解决心中的疑惑。

我们思考一下,序列化和反序列化的过程中,哪个流程最有可能有操作空间。

首先,序列化时,就是把对象转为二进制存在 ``ObjectOutputStream` 流中。这里,貌似好像没有什么特殊的地方。

其次,那就只能看反序列化了。反序列化时,需要从 ObjectInputStream 对象中读取对象,正常读出来的对象是一个新的不同的对象,为什么这次就能读出一个相同的对象呢,我猜这里会不会有什么猫腻?

应该是有可能的。所以,来到我们写的方法 getFromFile中,找到这一行ois.readObject()。它就是从流中读取对象的方法。

面试官看完我手写的单例直接惊呆了!

点进去,查看 ObjectInputStream.readObject 方法,然后找到 readObject0()方法

面试官看完我手写的单例直接惊呆了!

再点进去,我们发现有一个 switch 判断,找到 TC_OBJECT 分支。它是用来处理对象类型。

面试官看完我手写的单例直接惊呆了!

然后看到有一个 readOrdinaryObject方法,点进去。

面试官看完我手写的单例直接惊呆了!

然后找到这一行,isInstantiable() 方法,用来判断对象是否可实例化。

面试官看完我手写的单例直接惊呆了!

由于 cons 构造函数不为空,所以这个方法返回 true。因此构造出来一个 非空的 obj 对象 。

再往下走,调用,hasReadResolveMethod 方法去判断变量 readResolveMethod是否为非空。

面试官看完我手写的单例直接惊呆了!

面试官看完我手写的单例直接惊呆了!

我们去看一下这个变量,在哪里有没有赋值。会发现有这样一段代码,

面试官看完我手写的单例直接惊呆了!

点进去这个方法 getInheritableMethod。发现它最后就是为了返回我们添加的readResolve 方法。

面试官看完我手写的单例直接惊呆了!

同时我们发现,这个方法的修饰符可以是 public , protected 或者 private(我们当前用的就是private)。但是,不允许使用 static 和 abstract 修饰。

再次回到 readOrdinaryObject方法,继续往下走,会发现调用了 invokeReadResolve 方法。此方法,是通过反射调用 readResolve方法,得到了 rep 对象。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpspfp.html