序列化並非Java語言獨有的機制,它表示的是將一個對象的狀態信息轉換成可傳輸或可存儲的數據格式的過程,當須要再次使用該對象時經過反序列化將對象還原。java
例如在有些場景下咱們可能須要將Java對象傳輸給網絡另外一端的JVM上使用,像調用RPC方法傳遞參數對象,或者有時咱們但願Java對象的生命週期能比JVM長,即便JVM中止運行了下一次重啓也能繼續使用,這時候能夠經過將Java對象序列化後保存到文件系統,下次須要使用時經過反序列化還原成以前的Java對象。數組
在平常開發中咱們常常接觸到的序列化主要有基於XML數據格式的序列化、基於JSON格式的序列化和Java原始的序列化,本文只討論Java原生支持的序列化。安全
要將Java對象序列化爲二進制字節碼,被序列化的對象必須實現java.io.Serializable
接口或者java.io.Externalizable
接口。網絡
java.io.Serializable
是一個空接口,僅起到一個標識做用,標識該對象能夠被序列化,當對實現了該接口的對象進行序列化時,會使用Java默認的序列化方式進行序列化。ide
java.io.Externalizable
接口則是Java提供的一種支持自定義序列化的方法,實現該接口必須由開發者提供void readExternal(ObjectInput in)
和void writeExternal(ObjectOutput out)
兩個方法的實現。函數
若是沒有實現這兩個接口中的其中一個,那麼在序列化時會拋出java.io.NotSerializableException
。源碼分析
一般實現Serializable接口是最簡單快速支持序列化的方案,能夠經過ObjectOutputStream.writeObject(Object obj)
方法將參數指定的obj對象序列化爲二進制字節碼,並把獲得的字節碼寫入一個目標輸出流,例如FileOutputStream
中。而使用ObjectInputStream.readObject()
方法則能夠從一個源輸入流中讀取字節序列,反序列化爲Java對象返回。佈局
package com.liang; import java.io.Serializable; public class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; private transient String address; private Person parent; public Person(String name, int age, String address) { this.name = name; this.age = age; this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Person getParent() { return parent; } public void setParent(Person parent) { this.parent = parent; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", address=" + address + ", parent=" + parent + "]"; } }
package com.liang; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializableTest { private static final String FILE_NAME = "temp.out"; public static void main(String[] args) { // 序列化 Person son = new Person("xiaoming", 10, "廣州"); Person parent = new Person("daming", 40, "上海"); son.setParent(parent); System.out.println("before serialization: " + son); try { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME)); oos.writeObject(son); oos.flush(); oos.close(); } catch (IOException e) { e.printStackTrace(); } // 反序列化 try { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME)); Person person = (Person) ois.readObject(); System.out.println("after deserialization: " + person); ois.close(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
程序運行結束,控制檯輸出:this
用UltraEdit或者MadEdit打開temp.out,結果顯示以下:編碼
幾點說明:
java.io.InvalidClassException
異常。在ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
方法上打個斷點,跟蹤函數的調用過程和相關變量變化,相關源碼以下:
public ObjectOutputStream(OutputStream out) throws IOException { //進行安全檢測 verifySubclass(); //bout是BlockDataOutputStream實例,充當底層字節數據容器 bout = new BlockDataOutputStream(out); //一個輕量級的哈希表,實現對象到整數值的映射 handles = new HandleTable(10, (float) 3.00); /** * 一個輕量級的對象映射到替代對象的哈希表, * 內部由一個HandleTable示例和一個對象數組組成, * 經過HandlerTable實現對象到整數的映射,再經過整數從數組對象中返回替代對象 */ subs = new ReplaceTable(10, (float) 3.00); //若是爲true序列化時調用writeObjectOverride()方法,不然調用writeObject0()方法 enableOverride = false; //寫入二進制流的頭部,包括「魔數」(0xACED)和版本(0x0005) writeStreamHeader(); //設置blkmode屬性爲true bout.setBlockDataMode(true); if (extendedDebugInfo) { debugInfoStack = new DebugTraceInfoStack(); } else { debugInfoStack = null; } }
//若是enableOverride值爲true則調用writeObjectOverride(obj)方法,不然執行writeObject0(obj,false)方法 public final void writeObject(Object obj) throws IOException { if (enableOverride) { writeObjectOverride(obj); return; } try { writeObject0(obj, false); } catch (IOException ex) { if (depth == 0) { writeFatalException(ex); } throw ex; } }
private void writeObject0(Object obj, boolean unshared) throws IOException { boolean oldMode = bout.setBlockDataMode(false); depth++; //遞歸深度加1 try { // handle previously written and non-replaceable objects int h; //obj爲null或者替代對象爲null時調用writeNull,寫入十六進制0x70 if ((obj = subs.lookup(obj)) == null) { writeNull(); return; } // else if (!unshared && (h = handles.lookup(obj)) != -1) { writeHandle(h); return; } //待序列化的對象是java.lang.Class實例則執行writeClass,寫入0x7六、類描述符並把當前obj放到HandleTable裏 else if (obj instanceof Class) { writeClass((Class) obj, unshared); return; } /** * 待序列化的對象是ObjectStreamClass實例則執行writeClassDesc,寫入給定類的描述符, * ObjectStreamClass對象用來提取序列化過程當中某個對象所屬類的元數據信息,例如類名、 * 序列化號等等。 */ else if (obj instanceof ObjectStreamClass) { writeClassDesc((ObjectStreamClass) obj, unshared); return; } //檢查是否須要對序列化的對象進行替換序列化 Object orig = obj; Class<?> cl = obj.getClass(); ObjectStreamClass desc; for (;;) { Class<?> repCl; desc = ObjectStreamClass.lookup(cl, true); if (!desc.hasWriteReplaceMethod() || (obj = desc.invokeWriteReplace(obj)) == null || (repCl = obj.getClass()) == cl) { break; } cl = repCl; } if (enableReplace) { Object rep = replaceObject(obj); if (rep != obj && rep != null) { cl = rep.getClass(); desc = ObjectStreamClass.lookup(cl, true); } obj = rep; } // 若是序列化對象被替換過則須要進行與wirteObject0()開頭相似的一些判斷和處理 if (obj != orig) { subs.assign(orig, obj); if (obj == null) { writeNull(); return; } else if (!unshared && (h = handles.lookup(obj)) != -1) { writeHandle(h); return; } else if (obj instanceof Class) { writeClass((Class) obj, unshared); return; } else if (obj instanceof ObjectStreamClass) { writeClassDesc((ObjectStreamClass) obj, unshared); return; } } //根據對象的實際類型執行不一樣的寫入操做 if (obj instanceof String) { //String對象序列化 writeString((String) obj, unshared); } else if (cl.isArray()) { //數組對象序列化 writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { //枚舉對象序列化 writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) { //實現Serializable接口的對象序列化 writeOrdinaryObject(obj, desc, unshared); } else { //不知足以上任一種狀況,拋出NotSerializableException if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } } finally { depth--; bout.setBlockDataMode(oldMode); } }
private void writeString(String str, boolean unshared) throws IOException { handles.assign(unshared ? null : str); //放入HandleTable long utflen = bout.getUTFLength(str); if (utflen <= 0xFFFF) { //0xFFFF = 65535 bout.writeByte(TC_STRING); //0x74 bout.writeUTF(str, utflen); //以UTF格式寫入二進制流,先寫入長度,再寫入字符串內容 } else { bout.writeByte(TC_LONGSTRING); //0x7C bout.writeLongUTF(str, utflen); //一樣以UTF格式先寫入長度再寫入內容 } }
private void writeArray(Object array, ObjectStreamClass desc, boolean unshared) throws IOException { bout.writeByte(TC_ARRAY); //0x75 writeClassDesc(desc, false); //寫入類描述符 handles.assign(unshared ? null : array); //將array對象放入HandleTable Class<?> ccl = desc.forClass().getComponentType(); //獲取數組的類型 //根據數組的實際類型執行不一樣的寫入操做 if (ccl.isPrimitive()) { //原始數據類型及其對應的包裝類 if (ccl == Integer.TYPE) { int[] ia = (int[]) array; bout.writeInt(ia.length); bout.writeInts(ia, 0, ia.length); } else if (ccl == Byte.TYPE) { byte[] ba = (byte[]) array; bout.writeInt(ba.length); bout.write(ba, 0, ba.length, true); } else if (ccl == Long.TYPE) { long[] ja = (long[]) array; bout.writeInt(ja.length); bout.writeLongs(ja, 0, ja.length); } else if (ccl == Float.TYPE) { float[] fa = (float[]) array; bout.writeInt(fa.length); bout.writeFloats(fa, 0, fa.length); } else if (ccl == Double.TYPE) { double[] da = (double[]) array; bout.writeInt(da.length); bout.writeDoubles(da, 0, da.length); } else if (ccl == Short.TYPE) { short[] sa = (short[]) array; bout.writeInt(sa.length); bout.writeShorts(sa, 0, sa.length); } else if (ccl == Character.TYPE) { char[] ca = (char[]) array; bout.writeInt(ca.length); bout.writeChars(ca, 0, ca.length); } else if (ccl == Boolean.TYPE) { boolean[] za = (boolean[]) array; bout.writeInt(za.length); bout.writeBooleans(za, 0, za.length); } else { throw new InternalError(); } } else { //引用類型數組的處理:寫入數組長度,遍歷數組依次寫入每一個對象元素 Object[] objs = (Object[]) array; int len = objs.length; bout.writeInt(len); if (extendedDebugInfo) { debugInfoStack.push( "array (class \"" + array.getClass().getName() + "\", size: " + len + ")"); } try { for (int i = 0; i < len; i++) { if (extendedDebugInfo) { debugInfoStack.push( "element of array (index: " + i + ")"); } try { writeObject0(objs[i], false); } finally { if (extendedDebugInfo) { debugInfoStack.pop(); } } } } finally { if (extendedDebugInfo) { debugInfoStack.pop(); } } } }
private void writeEnum(Enum<?> en, ObjectStreamClass desc, boolean unshared) throws IOException { bout.writeByte(TC_ENUM); //0x7E ObjectStreamClass sdesc = desc.getSuperDesc(); writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false); handles.assign(unshared ? null : en); writeString(en.name(), false); //序列化枚舉對象的name屬性 }
private void writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) throws IOException { if (extendedDebugInfo) { debugInfoStack.push( (depth == 1 ? "root " : "") + "object (class \"" + obj.getClass().getName() + "\", " + obj.toString() + ")"); } try { //若是該類描述符表示的對象不容許序列化,將拋出InvalidClassException desc.checkSerialize(); bout.writeByte(TC_OBJECT); //0x73 writeClassDesc(desc, false); handles.assign(unshared ? null : obj); //若是該對象實現了java.io.Externalizable接口而且不是動態代理產生的對象,則調用writeExternalData方法 if (desc.isExternalizable() && !desc.isProxy()) { writeExternalData((Externalizable) obj); } else { //不然調用writeSerialData()序列化對象 writeSerialData(obj, desc); } } finally { if (extendedDebugInfo) { debugInfoStack.pop(); } } }
private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException { //獲取表示被序列化對象的數據的佈局數組,父類在前 ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); for (int i = 0; i < slots.length; i++) { ObjectStreamClass slotDesc = slots[i].desc; //若是被序列化對象複寫了writeObject方法則執行if塊的邏輯,不然執行defaultWriteFields方法 if (slotDesc.hasWriteObjectMethod()) { PutFieldImpl oldPut = curPut; curPut = null; SerialCallbackContext oldContext = curContext; if (extendedDebugInfo) { debugInfoStack.push( "custom writeObject data (class \"" + slotDesc.getName() + "\")"); } try { curContext = new SerialCallbackContext(obj, slotDesc); bout.setBlockDataMode(true); slotDesc.invokeWriteObject(obj, this); bout.setBlockDataMode(false); bout.writeByte(TC_ENDBLOCKDATA); } finally { curContext.setUsed(); curContext = oldContext; if (extendedDebugInfo) { debugInfoStack.pop(); } } curPut = oldPut; } else { defaultWriteFields(obj, slotDesc); } } }
private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException { Class<?> cl = desc.forClass(); if (cl != null && obj != null && !cl.isInstance(obj)) { throw new ClassCastException(); } desc.checkDefaultSerialize(); //準備一個字節數組primVals int primDataSize = desc.getPrimDataSize(); if (primVals == null || primVals.length < primDataSize) { primVals = new byte[primDataSize]; } //獲取對象中的基本數據類型的數據並保存在primVals字節數組中 desc.getPrimFieldValues(obj, primVals); //將基本數據類型的數據寫入底層字節容器中 bout.write(primVals, 0, primDataSize, false); /** 獲取對應類的全部成員變量,ObjectStreamField類主要用來提取序列化過程當中某個對象內的字段的元數據 * 信息,例如字段的類型、類型代碼、簽名等等 */ ObjectStreamField[] fields = desc.getFields(false); //準備一個對象數組用來存放引用類型的成員變量 Object[] objVals = new Object[desc.getNumObjFields()]; int numPrimFields = fields.length - objVals.length; desc.getObjFieldValues(obj, objVals); //遍歷對象數組,遞歸對引用類型的字段調用writeObject0()方法進行序列化 for (int i = 0; i < objVals.length; i++) { if (extendedDebugInfo) { debugInfoStack.push( "field (class \"" + desc.getName() + "\", name: \"" + fields[numPrimFields + i].getName() + "\", type: \"" + fields[numPrimFields + i].getType() + "\")"); } try { writeObject0(objVals[i], fields[numPrimFields + i].isUnshared()); } finally { if (extendedDebugInfo) { debugInfoStack.pop(); } } } } /** * 以以前的代碼示例爲例,執行writeObjcet方法會執行到該方法,所以: * 1. 獲取son對象的基本類型的字段數據,並寫入到底層的字節容器中 * 2. 獲取son對象的引用類型字段數據,即parent對象,遞歸調用writeObjcet0()進行序列化 * 3. 若是Person類有父類,會一併獲取父類的字段數據,寫入順序按照先父類後子類寫入。 */
幾點說明: 1. 序列化開始時,首先寫入固定的magic number和版本號。對應temp.out開頭的AC ED 00 05
2. 根據待序列化對象或替代對象的類型分別執行不一樣的寫入方法,如null、原始類型、引用類型。 3. 寫入引用類型對象時,會寫入對象類型聲明標誌位、類描述聲明標記位、類描述信息(包括類名長度、序列號、serialVersionUID值、支持序列化標記、字段個數等等),而後從下到上遞歸序列化其父類的類描述信息。再以後是從父類中的成員屬性開始,從上到下遞歸序列化成員變量的信息。 4. 無論序列化哪些信息,都會在以前先寫入相應的標誌位,標識這是一個對象、一個描述符、數據長度亦或是數據類型等等。
參照以上源碼,能夠知道temp.out輸出文件的每1個或多個字節具體表明哪些信息:
AC ED 00 05 73 72 00 10 63 6F 6D 2E 6C 69 61 6E 67 2E 50 65 72 73 6F 6E 00 00 00 00 00 00 00 01 02 00 03 49 00 03 61 67 65 4C 00 04 6E 61 6D 65 74 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 4C 00 06 70 61 72 65 6E 74 74 00 12 4C 63 6F 6D 2F 6C 69 61 6E 67 2F 50 65 72 73 6F 6E 3B 78 70 00 00 00 0A 74 00 08 78 69 61 6F 6D 69 6E 67 73 71 00 7E 00 00 00 00 00 28 74 00 06 64 61 6D 69 6E 67 70
每兩個十六進制爲一個字節:
byte –> B, char->C, double->D, float->F, int->I, long->J, short->S, boolean->Z 類或者接口->L, 數組->[
接下來將會爲從父類到子類的順序,爲每一個可序列化的對象寫入實例數據信息,即保存對象序列化時的狀態。
反序列化入口函數爲ObjectInputStream.readObject()
,該方法做用只是判斷應該調用readObjectOverride()
仍是readObject0(boolean)
,而後在反序列化完成後,調用vlist成員變量的doCallbacks()
方法執行回調邏輯。
下面主要分析readObject0方法:
private Object readObject0(boolean unshared) throws IOException { //檢查是否採用Data Block模式讀取 boolean oldMode = bin.getBlockDataMode(); if (oldMode) { //採用Data Block模式讀取 //計算字節流中剩餘字節數,大於0或者沒有defaultDataEnd的值爲true則拋出OptionalDataException 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; //若是字節流中包含TC_RESET標記,調用handleReset()方法 while ((tc = bin.peekByte()) == TC_RESET) { bin.readByte(); handleReset(); } depth++; totalObjectRefs++; try { /** 根據讀取的標記執行反序列化操做; * 這裏須要注意的一個點是當標記爲TC_ARRAY、TC_ENUM、TC_OBJECT、TC_STRING、TC_LONGSTRING時 * 會對讀取的返回結果調用checkResolve方法,此時若是被反序列化的對象重寫了readResolve()方法 * 那麼就會執行readResolve()方法,該方法能夠用來防止單例對象經過序列化反序列化的方式破壞單 * 例模式。 */ 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 read throw 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); } }
從readObject0方法咱們沒有看到解析魔數和序列化版本號的方法,這是由於這一步操做是在實例化ObjectInputStream的構造函數中進行:
public ObjectInputStream(InputStream in) throws IOException { ...... readStreamHeader(); ...... } protected void readStreamHeader() throws IOException, StreamCorruptedException { short s0 = bin.readShort(); short s1 = bin.readShort(); if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) { throw new StreamCorruptedException( String.format("invalid stream header: %04X%04X", s0, s1)); } }