序列化和反序列化的对单例破坏的防止及其原理
首先我們來看一下序列化和反序列化是怎么破壞單例的。看代碼
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接口。然后我們在測試類中測試一下怎么破壞。
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方法,下面看代碼:
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;} } 復制代碼此時我們再通過測試類進行測試即可發現我們通過序列化和反序列化得到的還是同一個對象。那么為什么添加一個這個方法就可以防止呢?下我們跟進去看看為什么
首先這個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 objectint 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()方法。
/*** 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這個方法的實現。
/*** 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是什么呢?我們繼續看
/** 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 suppressedthrow 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這個方法。這樣就算解決了我們最初的疑問了。同學們可以根據我說的源碼在相應的地方打斷點看看。
總結
以上是生活随笔為你收集整理的序列化和反序列化的对单例破坏的防止及其原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux使用nginx负载udp
- 下一篇: Nginx安装echo模块