­

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();     
  1. 序列化
  • 在調用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]);
    }