序列化和反序列化的對單例破壞的防止及其原理

首先咱們來看一下序列化和反序列化是怎麼破壞單例的。看代碼java

public class HungrySingleton implements Serializable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton(){
        if(hungrySingleton != null){
            throw new RuntimeException("單例構造器禁止反射調用");
        }
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}
複製代碼

這裏咱們使用以前的餓漢式的單例做爲例子。在以前餓漢式的代碼上作點小改動。就是讓咱們的單例類實現 Serializable接口。而後咱們在測試類中測試一下怎麼破壞。app

public class SingletonTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        HungrySingleton instance = HungrySingleton.getInstance();

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        oos.writeObject(instance);

        File file = new File("singleton_file");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        HungrySingleton newInstance = (HungrySingleton) ois.readObject();

        System.out.println(instance == newInstance;
        
    }
    
}
複製代碼

這裏首先咱們使用正常的方式來獲取一個對象。經過序列化將對象寫入文件中,而後咱們經過反序列化的到一個對象,咱們再對比這個對象,輸出的內存地址和布爾結果都表示這不是同一個對象。也就說咱們經過使用序列化和反序列化破壞了這個單例,那咱們該如何防治呢?防治起來很簡單,只須要在單例類中添加一個readResolve方法,下面看代碼:dom

public class HungrySingleton implements Serializable,Cloneable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    private Object readResolve(){
        return hungrySingleton;
    }
}
複製代碼

此時咱們再經過測試類進行測試便可發現咱們經過序列化和反序列化獲得的仍是同一個對象。那麼爲何添加一個這個方法就能夠防止呢?下咱們跟進去看看爲何ide

首先這個readResolve方法不是object裏面的方法。咱們進咱們的測試類中去看看這行中的HungrySingleton newInstance = (HungrySingleton) ois.readObject()中的 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 {
            Object obj = readObject0(false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
複製代碼

咱們重點來看一下 Object obj = readObject0(false)這一行這裏調用了一個readObject0方法,咱們再深刻看一下這個readObject0方法的實現。測試

/** * Underlying readObject implementation. */
    private Object readObject0(boolean unshared) throws IOException {
  
    ....  
    
    //各類判斷邏輯咱們暫時無論
    
    switch (tc) {
                 switch (tc) {
                case TC_NULL:
                    return readNull();

                case TC_REFERENCE:
                    return readHandle(unshared);

                case TC_CLASS:
                    return readClass(unshared);

                case TC_CLASSDESC:
                case TC_PROXYCLASSDESC:
                    return readClassDesc(unshared);

                case TC_STRING:
                case TC_LONGSTRING:
                    return checkResolve(readString(unshared));

                case TC_ARRAY:
                    return checkResolve(readArray(unshared));

                case TC_ENUM:
                    return checkResolve(readEnum(unshared));

                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));

                case TC_EXCEPTION:
                    IOException ex = readFatalException();
                    throw new WriteAbortedException("writing aborted", ex);

                case TC_BLOCKDATA:
                case TC_BLOCKDATALONG:
                 }
            ....     
    
    }
複製代碼

咱們看這個 case TC_OBJECT: 也就是判斷爲object以後的代碼,checkResolve(readOrdinaryObject(unshared))這行先是調用了readOrdinaryObject()方法,而後將方法的返回值返回給checkResolve方法,咱們先查看一下readOrdinaryObject()方法。ui

/** * Reads and returns "ordinary" (i.e., not a String, Class, * ObjectStreamClass, array, or enum constant) object, or null if object's * class is unresolvable (in which case a ClassNotFoundException will be * associated with object's handle). Sets passHandle to object's assigned * handle. */
    private Object readOrdinaryObject(boolean unshared){
        
        .....
        //各類判斷校驗
        
         Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        passHandle = handles.assign(unshared ? unsharedMarker : obj);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }
        
        .....
        
        
            return obj;
    }

複製代碼

咱們看一下 obj = desc.isInstantiable() ? desc.newInstance() : null這一行中的obj對象是幹嗎用的 咱們往下翻在這個方法的最後將這個obj返回出去了。咱們又回頭看這個這一行obj = desc.isInstantiable() ? desc.newInstance() : null 這個進行判斷若是 obj==desc.isInstantiable()就返回一個新的對象,不然返回空,代碼看到這裏好像有點眉目,我再看看isInstantiable這個方法的實現。this

/** * Returns true if represented class is serializable/externalizable and can * be instantiated by the serialization runtime--i.e., if it is * externalizable and defines a public no-arg constructor, or if it is * non-externalizable and its first non-serializable superclass defines an * accessible no-arg constructor. Otherwise, returns false. */
    boolean isInstantiable() {
        requireInitialized();
        return (cons != null);
    }

複製代碼

isInstantiable方法實現很簡單,這裏的cons是什麼呢?咱們繼續看spa

/** serialization-appropriate constructor, or null if none */
    private Constructor<?> cons;
複製代碼

cons是構造器這裏是經過反射獲取的對象,光看着一行代碼咱們好像並不能看出啥東西,這時候咱們看一下這一行代碼的註釋。 翻譯過來的話就是:翻譯

若是表示的類是serializable/externalizable而且能夠由序列化運行時實例化,則返回true - 若是它是可外部化的而且定義了公共的無參數構造函數,或者它是不可外化的,而且它的第一個非可序列化的超類定義了可訪問的無參數構造函數。不然,返回false。

externalizable這個類是serializable的一個子類用於制定序列化,好比自定義某個屬性的序列化,用的比較少。 好,咱們的單例實現了serializable接口因此這裏返回的是true,那麼回到咱們以前看看到的那裏,也就是這裏obj = desc.isInstantiable() ? desc.newInstance() : null 此時返回的就是一個newInstance是經過反射拿到的對象,既然是反射拿到的對象天然是一個新的對象,看到這裏咱們算弄明白了爲何序列化獲取的是一個新的對象。不過到這裏仍是沒有獲得咱們想要的知道的爲何寫了一個readResolve方法就能夠解決反序列化獲得的不是同一個對象的問題,那麼咱們繼續往下看ObjectInputSteam這個類

if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
複製代碼

看到這裏,這裏對obj進行了一次空判斷,這裏咱們剛分析了obj不會爲空,看這裏desc.hasReadResolveMethod()從命名咱們能夠看出這個判斷是判斷否包含readResolve這個方法。咱們再點進去看看這個的實現

/** * Returns true if represented class is serializable or externalizable and * defines a conformant readResolve method. Otherwise, returns false. */
    boolean hasReadResolveMethod() {
        requireInitialized();
        return (readResolveMethod != null);
    }
複製代碼

這裏依舊是看代碼沒啥看的,咱們看看註釋,符合咱們的猜想,也就是說這個

if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
複製代碼

判斷結果爲true那麼咱們再看看這個desc.invokeReadResolve(obj)的實現

/** * Invokes the readResolve method of the represented serializable class and * returns the result. Throws UnsupportedOperationException if this class * descriptor is not associated with a class, or if the class is * non-serializable or does not define readResolve. */
    Object invokeReadResolve(Object obj) throws IOException, UnsupportedOperationException {
        requireInitialized();
        if (readResolveMethod != null) {
            try {
                return readResolveMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                    throw new InternalError(th);  // never reached
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }
複製代碼

這裏咱們看方法名的也能猜想這是使用了反射來調用,看這一行 return readResolveMethod.invoke(obj, (Object[]) null) 使用了反射來調用readResolveMethod方法。但是你可能會問了 也沒看到用readResolveMethod這個方法啊,我對這個類進行搜索一下 readResolve

/** * Creates local class descriptor representing given class. */
    private ObjectStreamClass(final Class<?> cl) {
    
    .....
    
    
    
   domains = getProtectionDomains(cons, cl);
        writeReplaceMethod = getInheritableMethod(
            cl, "writeReplace", null, Object.class);
        readResolveMethod = getInheritableMethod(
            cl, "readResolve", null, Object.class);
        return null;
    
    ....    
複製代碼

在這裏能夠看到是獲取了readResolve這個方法。這樣就算解決了咱們最初的疑問了。同窗們能夠根據我說的源碼在相應的地方打斷點看看。

相關文章
相關標籤/搜索