单例设计模式-序列化破坏单例模式原理解析及解决方案
生活随笔
收集整理的這篇文章主要介紹了
单例设计模式-序列化破坏单例模式原理解析及解决方案
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
越來越成熟了,那是不是堅不可摧的呢,現在我們就要用序列號和反序列化來破壞單例模式,后面也會重點講一下原理,好好聽,讓我們來一起破壞單例模式吧,首先還是來到Test類里邊
package com.learn.design.pattern.creational.singleton;import java.io.Serializable;/*** 我們讓他來實現序列號接口* 具體序列話和反序列化的版本號呢* 我們就不關注了* 因為這個不是重點* * 我們現在的HungrySingleton實現了Serializable接口* 那么在ObjectStreamClass這里邊* 判斷就會返回true* boolean isInstantiable() {* requireInitialized();* return (cons != null);* }* 返回true之后再回來* * @author Leon.Sun**/
public class HungrySingleton implements Serializable,Cloneable{private final static HungrySingleton hungrySingleton;static{hungrySingleton = new HungrySingleton();}private HungrySingleton(){if(hungrySingleton != null){throw new RuntimeException("單例構造器禁止反射調用");}}public static HungrySingleton getInstance(){return hungrySingleton;}/*** 我們寫一個方法* 返回一個Object* * 從override里面根本就沒有* 他根本就不是Object的方法* 那方法為什么又叫readResolve呢* 叫別的名字可不可以呢* 那我們就較真* 為什么要這么寫* * * @return*/private Object readResolve(){/*** 然后在這里面返回一個單例對象* 我們回到Test里邊* 我們直接再run一下* 咱們一起來看* 最后看是否相等呢* 最后返回true* 這就很神奇了* 回到這個單例類里面* 你們肯定有一個疑問* 你為什么寫這個方法啊* * * */return hungrySingleton;}@Overrideprotected Object clone() throws CloneNotSupportedException {return getInstance();}
}
package com.learn.design.pattern.creational.singleton;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** 我們已經確定單例模式只能獲取一個對象* 就使用HungrySingleton這個類作為測試* * * @author Leon.Sun**/
public class Test {public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// LazySingleton lazySingleton = LazySingleton.getInstance();// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());// Thread t1 = new Thread(new T());
// Thread t2 = new Thread(new T());
// t1.start();
// t2.start();
// System.out.println("program end");/*** 從這里面拿了一個對象來* 那這個單例對象我們拿到了* 現在我們想象一下* 如果我們把這個instance序列號到一個文件中* 然后再從文件里取出來* 那這兩個對象還是同一個對象嗎* 那現在我們就來測試一下* * 首先單例獲取一個對象* * */HungrySingleton instance = HungrySingleton.getInstance();
// EnumInstance instance = EnumInstance.getInstance();
// instance.setData(new Object());
///*** 我們直接用ObjectOutputStream流* oos就是ObjectOutputStream三個字母的首字母小寫* new一個ObjectOutputStream* 里面new一個FileOutputStream* 名字我們起一個名字就叫singleton_file* 這個時候這里爆紅了* 應該是異常* 我們也不try catch了* 我們直接拋出* 然后呢繼續* * 看一下我們使用的類ObjectOutputStream* * 然后序列號* */ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));/*** 我們通過ObjectOutputStream對象寫一下* * */oos.writeObject(instance);
///*** 現在我們再讀一下他* new一個File* 文件名singleton_file* * */File file = new File("singleton_file");/*** 我們再通過ObjectInputStream讀取這個文件* 里面再放一個FileInputStream* 把這個file放進來* 這個時候我們通過ObjectInputStream獲取的這個對象去讀取他* 和上面聲明的instance做一個對比* 判斷他們是不是同一個對象* * ObjectInputStream* 我們重點看一下ois這個對象* 也就是ObjectInputStream這個類的對象* 他的readObject方法* 我們進來看一下* 看一下這個方法* 從上往下看* 重點看一下Object obj = readObject0(false);這一行的代碼* 他又調用readObject0這個方法* 我們進來看一下* private Object readObject0(boolean unshared)* 這個就是讀取對象* 各種邏輯不是我們要講的重點* 我們往下看* 這里面有一個switch* switch (tc)* 讀對應的類型* TC_STRING* TC_LONGSTRING* TC_ARRAY* TC_ENUM* TC_OBJECT* 那我們的是什么呢* 肯定是Object* TC_OBJECT* 所以我們的代碼會走到return checkResolve(readOrdinaryObject(unshared));這一行* 然后我們看一下他調的方法* 這一行調了兩個方法* 首先調了readOrdinaryObject這個方法* 然后把返回值放入checkResolve方法* 我們先進入readOrdinaryObject這個方法* private Object readOrdinaryObject(boolean unshared)* 往下看* 上面還是各種判斷校驗* 這里可以看到* if (cl == String.class || cl == Class.class* || cl == ObjectStreamClass.class) {* throw new InvalidClassException("invalid class descriptor");* }* 如果是String,* Class或者ObjectClass* 就會報一個異常* 無效的class* Object obj;* 然后我們看一下obj = desc.isInstantiable() ? desc.newInstance() : null;這一行* 這里面做了一個判斷* 首先我們看一下obj這個Object對象* 然后往下走* 看看他做什么用* return obj;* 我們看到把它返回回去了* 接著回來* 那我們就看這一行* obj = desc.isInstantiable() ? desc.newInstance() : null* 這里面做了一個判斷* 如果isInstantiable這個返回的是true的話* 就會生成一個新的對象* 否則返回null* 這個就是從readObject傳進來的* 所以我們看一下* isInstantiable()這個方法是做什么用的* 這里呢很簡單* boolean isInstantiable() {* requireInitialized();* return (cons != null);* }* return cons不等于空* cons是什么呢* private Constructor<?> cons;* 也就是反射的構造器類型* public final class Constructor<T> extends Executable* 回來* 從這行代碼看不出什么* 那這個時候我們就得看注釋了* 還好我們有注釋可以看* 這個注釋意思是說* Returns true if represented class is serializable/externalizable and can* 我們看一下serializable/externalizable* 包括下面還有be instantiated by the serialization runtime--i.e., if it is* 簡單的說呢* 如果serializable或者externalizable* 如果這樣的一個類class* 正在運行時被實例化* 那么該方法就會返回true* 所以returns true* 那這兩個序列化方式* serializable這個是比較全的* 而externalizable這個是定制哪些字段* 都可以通過它來定制* 那么看一下這個類* 他又繼承serializable* 他作為接口又是serializable的子類* 而我們平時實現的都是serializable這個類* 那平時定制序列號的情況* 也比較少見* 那就好理解了* 我們現在的HungrySingleton實現了Serializable接口* 那么在ObjectStreamClass這里邊* 判斷就會返回true* boolean isInstantiable() {* requireInitialized();* return (cons != null);* }* 返回true之后再回來 * obj = desc.isInstantiable() ? desc.newInstance() : null;* isInstantiable他為true的話就會newInstance* 然后把obj返回回去* 到這兒就比較清晰了* 這個對象是通過反射創建出來的對象* newInstance* ObjectStreamClass desc = readClassDesc(false);* desc是ObjectStreamClass類型的* 既然通過反射去創建對象* 那肯定和之前的對象不是同一個* 這也就是解釋了我剛剛為什么序列化和反序列化單例模式破壞了* 那到這里之后啊* 還是沒有找到我們想要的答案* 我們接著往下看* if (obj != null &&* handles.lookupException(passHandle) == null &&* desc.hasReadResolveMethod()) * 判斷obj不會為空* 然后進行一系列的判斷* 然后深入進去看了* 然后看看hasReadResolveMethod這個* 從這個名字就能夠看出來* 判斷它是否有ReadResolve方法* 我們進來看一下* boolean hasReadResolveMethod() {* requireInitialized();* return (readResolveMethod != null);* }* 這個也很簡單* 我們首先看一下這個對象* 他就是一個Method* private Method readResolveMethod;* return (readResolveMethod != null);* 也就是這一行通過源碼看不出什么* 那我們就看注釋* 注釋很有用* 平時看源碼的時候一定要養成看注釋的好習慣* Returns true if represented class is serializable or externalizable and* 返回true* 如果這個類是serializable這個接口或者externalizable這個接口類型* 實現了這個接口那class自然也是這個類型* and還有一個條件* defines a conformant readResolve method. Otherwise, returns false.* 那這個注釋已經說得很清楚了* 我們回來* 因為哦我們定義了這個方法* 所以desc.hasReadResolveMethod()這里判斷剩下* 那怎么調用那個方法呢* Object rep = desc.invokeReadResolve(obj);* 把obj傳進來* 進來看一下* 很明顯通過這個名字就能夠看出來* 它是通過反射來調用invokeReadResolve方法的* return readResolveMethod.invoke(obj, (Object[]) null);* 也就是說這個時候調用HungrySingleton里面寫的readResolve方法* 那么再回到ObjectStreamClass里邊* return readResolveMethod.invoke(obj, (Object[]) null);* 這個就是反射方法* 那這個方法名在哪里定義的呢* 抱著這個好奇心搜索這個關鍵字* readResolveMethod = getInheritableMethod(* cl, "readResolve", null, Object.class);* readObjectMethod賦值成什么呢* 名字賦成readResolve* readResolve他就是我們剛剛寫的目標方法名* 就在這里* 終于找到他了* 具體這個源碼怎么看呢* 我會debug根據源碼里面一行一行看一下* 你們也可以去看源碼* 這個是學習框架和源碼非常好的一種方式* 那回來* 所以這個方法是通過反射出來的* 也沒什么繼承關系* 所以只能把這個方法直接寫到這里* 一起來debug一下* 看源碼的時候也要看關鍵點* 現在一起debug一下* 我們跟一遍源碼* * 反序列化出來* */ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
///*** 強轉把異常拋出* * 這個時候可以調用readObject方法* 然后進入readObject0* 我們再打一個斷點* 之后就來到這里了* 我們看一下我們剛剛關注的Object這個case* case TC_OBJECT:* 看到進入到return checkResolve(readOrdinaryObject(unshared));* 然后readOrdinaryObject這個方法* 下邊繼續* obj = desc.isInstantiable() ? desc.newInstance() : null;* 我們看一下desc.isInstantiable()他的返回值是什么* 正如我們的預期他的返回值是true* 所以他會調一個newInstance* 我們單步走一下* 這個時候我們在內存里面看一下obj* Object obj;* 我們看到這個對象其實已經new出來了* HungrySingleton* 后面我記得還有一個斷點F8* desc.hasReadResolveMethod()* 判斷方法有沒有* 看來進入了if* Object rep = desc.invokeReadResolve(obj);* 開始invoke這個方法了* 進來* 注意if (readResolveMethod != null)這個* F8過來* 判斷他是不是空* 我們打開看一下* 這個Method類型* 可以看到name是readResolve* 他并不是空* 所以F6單步* return readResolveMethod.invoke(obj, (Object[]) null);* 這個時候反射調用這個方法* 會到HungrySingleton類里邊* private Object readResolve()* 這個時候就有了* F8這個時候* return hungrySingleton;* 這個時候就調用了這個方法* 而hungrySingleton這個對象我們看一下* 他本身已經創建好了* 是420* 而這個時候* return的時候* 就把這個對象返回回去了* 而不會剛剛通過反射newInstance* 這個時候return obj;* 返回的是hungrySingleton 420* 也就是HungrySingleton這里面的對象* F8繼續* 下邊我們就不看了* 剛剛講的這一串呢* 是序列號和反序列化的一個重點* 核心* 現在F8直接過* 回到Test里面看console一模一樣* 可是有一點* 我們考慮一下* 雖然最終返回的是同一個對象了* 但是在這個過程中* 其實它實例化對象了* 只是最后返回沒有返回而已* 所以一旦我們的業務場景涉及序列化和反序列化的時候* 一定要注意對單例的破壞* 這個呢非常重要* 希望通過這個能對序列化和反序列化單例的破壞有一個深入的理解* 并且學會如何看源碼* 如何找源碼的關鍵路徑* 這些技能對成長是非常有益處的* 我們接下來看一下對于反射這種情況* 我們如何來防御呢* * * */HungrySingleton newInstance = (HungrySingleton) ois.readObject();
// EnumInstance newInstance = (EnumInstance) ois.readObject();
//
// System.out.println(instance.getData());
// System.out.println(newInstance.getData());
// System.out.println(instance.getData() == newInstance.getData());// Class objectClass = HungrySingleton.class;
// Class objectClass = StaticInnerClassSingleton.class;// Class objectClass = LazySingleton.class;
// Class objectClass = EnumInstance.class;// Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);
//
// constructor.setAccessible(true);
// EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);//
// LazySingleton newInstance = (LazySingleton) constructor.newInstance();
// LazySingleton instance = LazySingleton.getInstance();// StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
// StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();// HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
// HungrySingleton instance = HungrySingleton.getInstance();/*** 在判斷之前呢* 我們把這個兩個對象都輸出一下* 我們run一下* 這里面會拋一個異常* 這個異常我們看到就知道了* 這里的異常說了* 不是可序列化的異常* 很簡單* 我們讓他來實現序列號接口* 剛剛這個異常是特意留出來的* 希望印象深刻* instance是001* newInstance002* 他們是不相等* 那目前來看就違背了單例模式的一個初衷* 通過序列化和反序列化拿到了不同的對象* 而我們只希望拿到同一個對象* 那這個事情要怎么解呢* 那解這個問題也不難* 重要的是理解* 理解他的原理是什么* 我們這里只是以餓漢式來做例子* 其他方式可以自己嘗試一下* 異曲同工* 那么來到這個單例類里面* * * */System.out.println(instance);System.out.println(newInstance);/*** instance和newInstance做一個對比* */System.out.println(instance == newInstance);// EnumInstance instance = EnumInstance.getInstance();
// instance.printTest();}
}
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;} finally {passHandle = outerHandle;if (closed && depth == 0) {clear();}}}
private Object readObject0(boolean unshared) throws IOException {boolean oldMode = bin.getBlockDataMode();if (oldMode) {int remain = bin.currentBlockRemaining();if (remain > 0) {throw new OptionalDataException(remain);} else if (defaultDataEnd) {/** Fix for 4360508: stream is currently at the end of a field* value block written via default serialization; since there* is no terminating TC_ENDBLOCKDATA tag, simulate* end-of-custom-data behavior explicitly.*/throw new OptionalDataException(true);}bin.setBlockDataMode(false);}byte tc;while ((tc = bin.peekByte()) == TC_RESET) {bin.readByte();handleReset();}depth++;totalObjectRefs++;try {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:if (oldMode) {bin.setBlockDataMode(true);bin.peek(); // force header readthrow new OptionalDataException(bin.currentBlockRemaining());} else {throw new StreamCorruptedException("unexpected block data");}case TC_ENDBLOCKDATA:if (oldMode) {throw new OptionalDataException(true);} else {throw new StreamCorruptedException("unexpected end of block data");}default:throw new StreamCorruptedException(String.format("invalid type code: %02X", tc));}} finally {depth--;bin.setBlockDataMode(oldMode);}}
private Object readOrdinaryObject(boolean unshared)throws IOException{if (bin.readByte() != TC_OBJECT) {throw new InternalError();}ObjectStreamClass desc = readClassDesc(false);desc.checkDeserialize();Class<?> cl = desc.forClass();if (cl == String.class || cl == Class.class|| cl == ObjectStreamClass.class) {throw new InvalidClassException("invalid class descriptor");}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);}if (desc.isExternalizable()) {readExternalData((Externalizable) obj, desc);} else {readSerialData(obj, desc);}handles.finish(passHandle);if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {// Filter the replacement objectif (rep != null) {if (rep.getClass().isArray()) {filterCheck(rep.getClass(), Array.getLength(rep));} else {filterCheck(rep.getClass(), -1);}}handles.setObject(passHandle, obj = rep);}}return obj;}
/*** 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);}
/*** Returns true if represented class is serializable (but not* externalizable) and defines a conformant writeObject method. Otherwise,* returns false.*/boolean hasWriteObjectMethod() {requireInitialized();return (writeObjectMethod != null);}
/*** 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();}}
if (serializable) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {if (isEnum) {suid = Long.valueOf(0);fields = NO_FIELDS;return null;}if (cl.isArray()) {fields = NO_FIELDS;return null;}suid = getDeclaredSUID(cl);try {fields = getSerialFields(cl);computeFieldOffsets();} catch (InvalidClassException e) {serializeEx = deserializeEx =new ExceptionInfo(e.classname, e.getMessage());fields = NO_FIELDS;}if (externalizable) {cons = getExternalizableConstructor(cl);} else {cons = getSerializableConstructor(cl);writeObjectMethod = getPrivateMethod(cl, "writeObject",new Class<?>[] { ObjectOutputStream.class },Void.TYPE);readObjectMethod = getPrivateMethod(cl, "readObject",new Class<?>[] { ObjectInputStream.class },Void.TYPE);readObjectNoDataMethod = getPrivateMethod(cl, "readObjectNoData", null, Void.TYPE);hasWriteObjectData = (writeObjectMethod != null);}domains = getProtectionDomains(cons, cl);writeReplaceMethod = getInheritableMethod(cl, "writeReplace", null, Object.class);readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);return null;}});} else {suid = Long.valueOf(0);fields = NO_FIELDS;}
?
總結
以上是生活随笔為你收集整理的单例设计模式-序列化破坏单例模式原理解析及解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单例设计模式-饿汉式
- 下一篇: 单例设计模式-反射攻击解决方案及原理分析