序列化破坏单例
一个对象被创建好之后,有时候需要进行序列化,方便传输或者存在硬盘中,下次需要使用的时候直接可以从硬盘数据中反序列化得到对象。而反序列化后的对象的内存会重新分配,即是重新创建。但是如果序列化的是一个单例对象,就违背了单例模式的原则,这就叫做序列化破坏单例。
单例模式代码
package com.entity;
import java.io.Serializable;
/**
* @Classname LazyInnerSingleton_1 优化后的代码
* @Description TODO
* @Date 2020/4/3 17:15
* @Created by ASUS
* 该类只允许创建一个单例的实例对象,创建多个会抛出 RuntimeException
*/
public class LazyInnerSingleton_1 implements Serializable {
private LazyInnerSingleton_1(){
if(InnerClass.INSTANCE != null){
throw new RuntimeException("LazyInnerSingleton_1类已经存在一个实例化对象,不允许创建多个对象");
}
}
public static final LazyInnerSingleton_1 getInstance(){
return InnerClass.INSTANCE;
}
private static final class InnerClass{
// 该实例不会被序列化到文件,保存在静态区中
private static final LazyInnerSingleton_1 INSTANCE = new LazyInnerSingleton_1();
}
}
序列化破坏测试
// 序列化破坏单例
private static void DisturbLazyInner2() {
LazyInnerSingleton_1 lazyInnerSingleton_1 = LazyInnerSingleton_1.getInstance();
LazyInnerSingleton_1 lazyInnerSingleton_12 = null;
try {
OutputStream os = new FileOutputStream(new File("config/test.txt"));
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(lazyInnerSingleton_1);
oos.flush();
oos.close();
os.close();
InputStream is = new FileInputStream(new File("config/test.txt"));
ObjectInputStream ois = new ObjectInputStream(is);
lazyInnerSingleton_12 = (LazyInnerSingleton_1) ois.readObject();
ois.close();
is.close();
// lazyInnerSingleton_1与lazyInnerSingleton_12是两个不同的对象,
// 但拥有同一静态实例对象 private static final LazyInnerSingleton_1 INSTANCE
System.out.println(lazyInnerSingleton_1 == lazyInnerSingleton_12.getInstance());//true
System.out.println(lazyInnerSingleton_1 == lazyInnerSingleton_12);//false
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
上述的结果显示:单例对象被序列化到文件中,被持久化后,反序列化之后,得到的两个对象是不一致的,即是单例被破坏。
那么如何将这个问题解决呢?比较方便的方法是:在之前的单例类中添加
private Object readResolve(){
return InnerClass.INSTANCE;
}
即可解决这个问题,仅仅3行代码就可以。但是底层如何完成这个工作的呢?下面我们来探究一下JDK的实现源码吧。
源码探究
大体的执行流程如图所示:
首先进入ObjectInputStream的readObject()方法:
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
//关键代码,readObject0()
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
freeze();
}
return obj;
}
....
}
其中它调用了readObject0()的方法,进入:
private Object readObject0(boolean unshared) throws IOException {
...
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
...
同样去看关键代码:readOrdinaryObject()中的关键代码
...
obj = desc.isInstantiable() ? desc.newInstance() : null;
...
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
...
}
...
而isInstantiable():
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
很简单,就是判断对象是否具有构造函数,有则返回true,否则则返回false.
显然我们的类是具有构造函数的,即是返回true,之后就创建对象实例,赋值给obj.在if判断中调用hasReadResolveMethod()方法,很显然该方法是用来判断是否存在readResolve()方法的。
我们进入它的具体实现中:
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
逻辑很简单,就是判断是否存在readResolve()方法的,存在返回true.
那么 readResolveMethod在哪里赋值的呢?我们在ObjectStreamClass的构造方法中找到了下列的赋值语句
readResolveMethod =
getInheritableMethod(cl, "readResolve", null, Object.class);
咱们进一步探究:进入getInheritableMethod
private static Method getInheritableMethod(...){
...
try {
meth = defCl.getDeclaredMethod(name, argTypes);
break;
}
...
}
这里我们就可以确定该方法是通过反射的方式去拿readResovle()方法。
我们返回到之前的地方去:
下面进desc.invokeReadResolve(obj):
Object invokeReadResolve(Object obj)
throws IOException, UnsupportedOperationException
{
...
try {
return readResolveMethod.invoke(obj, (Object[]) null);
}
...
到这里我们就可清晰地看到返回的对象是通过反射拿到的readResolve()方法,然后调用它。回到我们的源代码中,我们将该方法的返回值设定为静态的单例对象,而新创建的对象并没返回,如果没有readResolve()方法,就会返回新创建的对象。这就是我们添加readResolve()即可解决序列化破坏单例的根本原因。