Java中的序列化破坏单例——JDK源码探究

序列化破坏单例

一个对象被创建好之后,有时候需要进行序列化,方便传输或者存在硬盘中,下次需要使用的时候直接可以从硬盘数据中反序列化得到对象。而反序列化后的对象的内存会重新分配,即是重新创建。但是如果序列化的是一个单例对象,就违背了单例模式的原则,这就叫做序列化破坏单例。

单例模式代码

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()即可解决序列化破坏单例的根本原因。

猜你喜欢

转载自blog.csdn.net/qq_45744501/article/details/105306226