transient的作用及序列化
1.transient 介紹
Java中的transient關鍵字,transient是短暫的意思。對於transient 修飾的成員變量,在類的實例對象的序列化處理過程中會被忽略。 因此,transient變量不會貫穿對象的序列化和反序列化,生命周期僅存於調用者的內存中而不會寫到磁盤裡進行持久化。
(1)序列化
Java中對象的序列化指的是將對象轉換成以位元組序列的形式來表示,這些位元組序列包含了對象的數據和信息,一個序列化後的對象可以被寫到數據庫或文件中,也可用於網絡傳輸。一般地,當我們使用緩存cache(內存空間不夠有可能會本地存儲到硬盤)或遠程調用rpc(網絡傳輸)的時候,經常需要讓實體類實現Serializable接口,目的就是為了讓其可序列化。當然,序列化後的最終目的是為了反序列化,恢復成原先的Java對象實例。所以序列化後的位元組序列都是可以恢復成Java對象的,這個過程就是反序列化。
(2)為什麼要用transient關鍵字?
在持久化對象時,對於一些特殊的數據成員(如用戶的密碼,銀行卡號等),我們不想用序列化機制來保存它。為了在一個特定對象的一個成員變量上關閉序列化,可以在這個成員變量前加上關鍵字transient。
(3)transient的作用
transient是Java語言的關鍵字,用來表示一個成員變量不是該對象序列化的一部分。當一個對象被序列化的時候,transient型變量的值不包括在序列化的結果中。而非transient型的變量是被包括進去的。 注意static修飾的靜態變量天然就是不可序
2. transient 使用總結
(1)一旦變量被transient修飾,變量將不再是對象持久化的一部分,該變量內容在序列化後無法被訪問。
(2) transient關鍵字只能修飾變量,而不能修飾方法和類。注意,本地變量是不能被transient關鍵字修飾的。變量如果是用戶自定義類變量,則該類需要實現Serializable接口。
(3)一個靜態變量不管是否被transient修飾,均不能被序列化(如果反序列化後類中static變量還有值,則值為當前JVM中對應static變量的值)。序列化保存的是對象狀態,靜態變量保存的是類狀態,因此序列化並不保存靜態變量。
3. 序列化及反序列化
- 序列化
是指將Java對象保存為二進制位元組碼的過程。
- 反序列化
將二進制位元組碼重新轉成Java對象的過程。
(1)為什麼序列化
- 我們知道,一般Java對象的生命周期比Java虛擬機短,而實際的開發中,我們需要
在Jvm停止後能夠繼續持有對象,這個時候就需要用到序列化技術將對象持久到磁盤或數據庫。
- 在多個項目進行RPC調用的,需要在網絡上傳輸JavaBean對象。我們知道數據只能以二進制的
形式才能在網絡上進行傳輸。所以也需要用到序列化技術。
(2)序列化的底層原理
1. 程序入口:
writeObject(obj)
Student stu1 = new Student(1001, "jack", "play"); Student stu2 = new Student(1002, "tom", "sleep"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\stu.dat")); oos.writeObject(stu1); oos.writeObject(stu2); oos.close();
- 序列化
- 在調用writeObject()方法之前,會先調用ObjectOutputStream的構造函數,生成
一個ObjectOutputStream對象。
public ObjectOutputStream(OutputStream out) throws IOException { verifySubclass(); // bout是底層的數據位元組容器 bout = new BlockDataOutputStream(out); handles = new HandleTable(10, (float) 3.00); subs = new ReplaceTable(10, (float) 3.00); enableOverride = false; // 寫入序列化文件頭 writeStreamHeader(); // 設置文件緩存刷新配置 bout.setBlockDataMode(true); if (extendedDebugInfo) { debugInfoStack = new DebugTraceInfoStack(); } else { debugInfoStack = null; } }
writeStreamHeader的方法內容如下:
protected void writeStreamHeader() throws IOException { bout.writeShort(STREAM_MAGIC); bout.writeShort(STREAM_MAGIC); }
3. 調用writeObject()方法進行具體的序列化寫入
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; } }
- writeObject0的具體內容
private void writeObject0(Object obj, boolean unshared) throws IOException { boolean oldMode = bout.setBlockDataMode(false); depth++; try { // handle previously written and non-replaceable objects int h; if ((obj = subs.lookup(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; } // check for replacement object Object orig = obj; // 需要序列的對象的Class對象 Class<?> cl = obj.getClass(); ObjectStreamClass desc; for (;;) { // 提示:跳過檢查string和數組 // REMIND: skip this check for strings/arrays? Class<?> repCl; // 創建描述c1的ObjectStreamClass對象 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; } // if object replaced, run through original checks a second time 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; } } // remaining cases // 根據實際要寫入的類型,進行不同的寫入操作 // 由此可以看出String、Array、Enum是直接寫入操作的 if (obj instanceof 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 { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } } finally { depth--; bout.setBlockDataMode(oldMode); } }
從上面可以看出主要做了兩件事
- 創建了ObjectStreamClass對象
- 根據實際要寫入的類型,進行不同的寫入操作
writeOrdinaryObject()
為什麼說序列化並不安全
因為序列化的對象數據轉換為二進制,並且完全可逆。但是在RMI調用時
所有private字段的數據都以明文二進制的形式出現在網絡的套接字上,這顯然是不安全的
解決方案:
- 1、 序列化Hook化(移位和複位)
- 2、 序列數據加密和簽名
- 3、 利用transient的特性解決
- 4、 打包和解包代理
4. transient 在序列化底層的應用
static和transient修飾的字段不能被序列化。
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) { Field[] clFields = cl.getDeclaredFields(); ArrayList<ObjectStreamField> list = new ArrayList<>(); int mask = Modifier.STATIC | Modifier.TRANSIENT; for (int i = 0; i < clFields.length; i++) { if ((clFields[i].getModifiers() & mask) == 0) { list.add(new ObjectStreamField(clFields[i], false, true)); } } int size = list.size(); return (size == 0) ? NO_FIELDS : list.toArray(new ObjectStreamField[size]); }