<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Java 序列化/反序列化

    在Java中實現對象反序列化非常簡單,實現java.io.Serializable(內部序列化)java.io.Externalizable(外部序列化)接口即可被序列化(Externalizable接口只是實現了java.io.Serializable接口)。

    反序列化類對象時有如下限制:

    1. 被反序列化的類必須存在。
    2. serialVersionUID值必須一致。

    除此之外,反序列化類對象是不會調用該類構造方法的,因為在反序列化創建類實例時使用了sun.reflect.ReflectionFactory.newConstructorForSerialization創建了一個反序列化專用的Constructor(反射構造方法對象),使用這個特殊的Constructor可以繞過構造方法創建類實例(前面章節講sun.misc.Unsafe 的時候我們提到了使用allocateInstance方法也可以實現繞過構造方法創建類實例)。

    使用反序列化方式創建類實例代碼片段:

    package com.anbai.sec.serializes;
    
    import sun.reflect.ReflectionFactory;
    
    import java.lang.reflect.Constructor;
    
    /**
     * 使用反序列化方式在不調用類構造方法的情況下創建類實例
     * Creator: yz
     * Date: 2019/12/20
     */
    public class ReflectionFactoryTest {
    
        public static void main(String[] args) {
            try {
                // 獲取sun.reflect.ReflectionFactory對象
                ReflectionFactory factory = ReflectionFactory.getReflectionFactory();
    
                // 使用反序列化方式獲取DeserializationTest類的構造方法
                Constructor constructor = factory.newConstructorForSerialization(
                        DeserializationTest.class, Object.class.getConstructor()
                );
    
                // 實例化DeserializationTest對象
                System.out.println(constructor.newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }
    

    程序運行結果:

    com.anbai.sec.serializes.DeserializationTest@2b650cea
    

    具體細節可參考 不用構造方法也能創建對象

    ObjectInputStream、ObjectOutputStream

    java.io.ObjectOutputStream類最核心的方法是writeObject方法,即序列化類對象。

    java.io.ObjectInputStream類最核心的功能是readObject方法,即反序列化類對象。

    所以,只需借助ObjectInputStreamObjectOutputStream類我們就可以實現類的序列化和反序列化功能了。

    java.io.Serializable

    java.io.Serializable是一個空的接口,我們不需要實現java.io.Serializable的任何方法,代碼如下:

    public interface Serializable {
    }
    

    您可能會好奇我們實現一個空接口有什么意義?其實實現java.io.Serializable接口僅僅只用于標識這個類可序列化。實現了java.io.Serializable接口的類原則上都需要生產一個serialVersionUID常量,反序列化時如果雙方的serialVersionUID不一致會導致InvalidClassException 異常。如果可序列化類未顯式聲明 serialVersionUID,則序列化運行時將基于該類的各個方面計算該類的默認 serialVersionUID值。

    DeserializationTest.java測試代碼如下:

    package com.anbai.sec.serializes;
    
    import java.io.*;
    import java.util.Arrays;
    
    /**
     * Creator: yz
     * Date: 2019/12/15
     */
    public class DeserializationTest implements Serializable {
    
        private String username;
    
        private String email;
    
        // 省去get/set方法....
    
        public static void main(String[] args) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
    
            try {
                // 創建DeserializationTest類,并類設置屬性值
                DeserializationTest t = new DeserializationTest();
                t.setUsername("yz");
                t.setEmail("admin@javaweb.org");
    
                // 創建Java對象序列化輸出流對象
                ObjectOutputStream out = new ObjectOutputStream(baos);
    
                // 序列化DeserializationTest類
                out.writeObject(t);
                out.flush();
                out.close();
    
                // 打印DeserializationTest類序列化以后的字節數組,我們可以將其存儲到文件中或者通過Socket發送到遠程服務地址
                System.out.println("DeserializationTest類序列化后的字節數組:" + Arrays.toString(baos.toByteArray()));
    
                // 利用DeserializationTest類生成的二進制數組創建二進制輸入流對象用于反序列化操作
                ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    
                // 通過反序列化輸入流(bais),創建Java對象輸入流(ObjectInputStream)對象
                ObjectInputStream in = new ObjectInputStream(bais);
    
                // 反序列化輸入流數據為DeserializationTest對象
                DeserializationTest test = (DeserializationTest) in.readObject();
                System.out.println("用戶名:" + test.getUsername() + ",郵箱:" + test.getEmail());
    
                // 關閉ObjectInputStream輸入流
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    
    }
    

    程序執行結果如下:

    DeserializationTest類序列化后的字節數組:[-84, -19, 0, 5, 115, 114, 0, 44, 99, 111, 109, 46, 97, 110, 98, 97, 105, 46, 115, 101, 99, 46, 115, 101, 114, 105, 97, 108, 105, 122, 101, 115, 46, 68, 101, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 84, 101, 115, 116, 74, 36, 49, 16, -110, 39, 13, 76, 2, 0, 2, 76, 0, 5, 101, 109, 97, 105, 108, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 0, 8, 117, 115, 101, 114, 110, 97, 109, 101, 113, 0, 126, 0, 1, 120, 112, 116, 0, 17, 97, 100, 109, 105, 110, 64, 106, 97, 118, 97, 119, 101, 98, 46, 111, 114, 103, 116, 0, 2, 121, 122]
    用戶名:yz,郵箱:admin@javaweb.org
    

    核心邏輯其實就是使用ObjectOutputStream類的writeObject方法序列化DeserializationTest類,使用ObjectInputStream類的readObject方法反序列化DeserializationTest類而已。

    簡化后的代碼片段如下:

    // 序列化DeserializationTest類
    ObjectOutputStream out = new ObjectOutputStream(baos);
    out.writeObject(t);
    
    // 反序列化輸入流數據為DeserializationTest對象
    ObjectInputStream in = new ObjectInputStream(bais);
    DeserializationTest test = (DeserializationTest) in.readObject();
    

    ObjectOutputStream序列化類對象的主要流程是首先判斷序列化的類是否重寫了writeObject方法,如果重寫了就調用序列化對象自身的writeObject方法序列化,序列化時會先寫入類名信息,其次是寫入成員變量信息(通過反射獲取所有不包含被transient修飾的變量和值)。

    java.io.Externalizable

    java.io.Externalizablejava.io.Serializable幾乎一樣,只是java.io.Externalizable接口定義了writeExternalreadExternal方法需要序列化和反序列化的類實現,其余的和java.io.Serializable并無差別。

    java.io.Externalizable.java:

    public interface Externalizable extends java.io.Serializable {
    
      void writeExternal(ObjectOutput out) throws IOException;
    
      void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
    
    }
    

    ExternalizableTest.java測試代碼如下:

    package com.anbai.sec.serializes;
    
    import java.io.*;
    import java.util.Arrays;
    
    /**
     * Creator: yz
     * Date: 2019/12/15
     */
    public class ExternalizableTest implements java.io.Externalizable {
    
        private String username;
    
        private String email;
    
        // 省去get/set方法....
    
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(username);
            out.writeObject(email);
        }
    
        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            this.username = (String) in.readObject();
            this.email = (String) in.readObject();
        }
    
        public static void main(String[] args) {
            // 省去測試代碼,因為和DeserializationTest一樣...
        }
    
    }
    

    程序執行結果如下:

    ExternalizableTest類序列化后的字節數組:[-84, -19, 0, 5, 115, 114, 0, 43, 99, 111, 109, 46, 97, 110, 98, 97, 105, 46, 115, 101, 99, 46, 115, 101, 114, 105, 97, 108, 105, 122, 101, 115, 46, 69, 120, 116, 101, 114, 110, 97, 108, 105, 122, 97, 98, 108, 101, 84, 101, 115, 116, -122, 124, 92, -120, -52, 73, -100, 6, 12, 0, 0, 120, 112, 116, 0, 2, 121, 122, 116, 0, 17, 97, 100, 109, 105, 110, 64, 106, 97, 118, 97, 119, 101, 98, 46, 111, 114, 103, 120]
    ExternalizableTest類反序列化后的字符串:??sr+com.anbai.sec.serializes.ExternalizableTest?|\??I?xptyztadmin@javaweb.orgx
    用戶名:yz,郵箱:admin@javaweb.org
    

    鑒于兩者之間沒有多大差別,這里就不再贅述。

    自定義序列化(writeObject)和反序列化(readObject)

    實現了java.io.Serializable接口的類還可以定義如下方法(反序列化魔術方法)將會在類序列化和反序列化過程中調用:

    1. private void writeObject(ObjectOutputStream oos),自定義序列化。
    2. private void readObject(ObjectInputStream ois),自定義反序列化。
    3. private void readObjectNoData()
    4. protected Object writeReplace(),寫入時替換對象。
    5. protected Object readResolve()

    具體的方法名定義在java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>)方法有詳細的聲明。

    序列化時可自定義的方法示例代碼:

    public class DeserializationTest implements Serializable {
    
    /**
         * 自定義反序列化類對象
         *
         * @param ois 反序列化輸入流對象
         * @throws IOException            IO異常
         * @throws ClassNotFoundException 類未找到異常
         */
        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
            System.out.println("readObject...");
    
            // 調用ObjectInputStream默認反序列化方法
            ois.defaultReadObject();
    
            // 省去調用自定義反序列化邏輯...
        }
    
        /**
         * 自定義序列化類對象
         *
         * @param oos 序列化輸出流對象
         * @throws IOException IO異常
         */
        private void writeObject(ObjectOutputStream oos) throws IOException {
            oos.defaultWriteObject();
    
            System.out.println("writeObject...");
            // 省去調用自定義序列化邏輯...
        }
    
        private void readObjectNoData() {
            System.out.println("readObjectNoData...");
        }
    
        /**
         * 寫入時替換對象
         *
         * @return 替換后的對象
         */
        protected Object writeReplace() {
            System.out.println("writeReplace....");
    
            return null;
        }
    
        protected Object readResolve() {
            System.out.println("readResolve....");
    
            return null;
        }
    
    }
    

    當我們序列化DeserializationTest類時,會自動調用(反射)該類的writeObject(ObjectOutputStream oos)方法,反序列化時候也會自動調用readObject(ObjectInputStream)方法,也就是說我們可以通過在需要序列化/反序列化的類中定義readObjectwriteObject方法從而實現自定義的序列化和反序列化操作,和當然前提是被序列化的類必須有此方法且方法的修飾符必須是private

    本文章首發在 網安wangan.com 網站上。

    上一篇 下一篇
    討論數量: 0
    只看當前版本


    暫無話題~
    亚洲 欧美 自拍 唯美 另类