Java反序列化時是否通過默認構造函數創建對象?


問題背景

今天在解決一個對象的持久化問題時,需要用到序列化技術。一開始,我想用 fastjson,但是麻煩的是這個框架是基於 getter 方法來序列化對象的,可是我序列化的對象不是一個標准的 Java Bean 對象,沒有 getter/setter 方法。而我的需求是根據字段(類成員變量)來序列化對象。然后我就想到了使用 Java 序列化技術,並且配合使用 transient 屏蔽不需要參與序列化的字段(屬性)。更多 transient 關鍵字的信息可以參考這篇文章:transient 變量修飾符
但是問題來了,先上代碼:

// User 類是我要序列化保存,並且再用反序列化恢復的類
public class User implements Serializable {

    private String userName;

    private transient List<String> tags;

    public User(String userName) {
        this.userName = userName;
        this.tags = new ArrayList<String>();
    }

    public User(String userName, List<String> tags) {
        this.userName = userName;
        this.tags = tags;
    }

    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", tags=" + tags +
                '}';
    }
}

我的測試類是

public class JavaSerializationTest {

    @Test
    public void test1() {
        User user = new User("kendoziyu", Arrays.asList("cool", "smart"));
        byte[] data = SerializationUtils.serialize(user);

        User copyUser = SerializationUtils.deserialize(data);
        System.out.println(copyUser);

        User expiredUser = new User("kendoziyu");
        System.out.println(expiredUser);
    }
}

結果:

問題所在:
我希望反序列化之后 tags 對象是一個空集合,但不是一個 null!

后來我也找到了替代方案:
Externalizable 是 Serializable 的子類接口,Externalizable 是一個手動序列化接口,而 Serializable 是一個自動序列化接口。

public class NewUser implements Externalizable {

    private String userName;

    private List<String> tags;

    public NewUser() {
        tags = new ArrayList<String>();
    }

    public NewUser(String userName) {
        this.userName = userName;
        this.tags = new ArrayList<String>();
    }

    public NewUser(String userName, List<String> tags) {
        this.userName = userName;
        this.tags = tags;
    }

    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", tags=" + tags +
                '}';
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(this.userName);
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.userName = (String) in.readObject();
    }
}
  • 此時不需要加入 transient 關鍵字了,取而代之的是,在 writeExternal 方法中,手動編碼控制需要序列化哪些字段。
  • 這個方法需要一個默認的構造函數,否則會拋出異常 org.apache.commons.lang3.SerializationException: java.io.InvalidClassException: test.java.serialization.NewUser; no valid constructor
  • 當然有了默認構造函數之后,我們就可以在默認構造函數中,初始化我們的集合

產生疑惑

問題雖然解決了,但是同樣是 Serializable 接口,為什么一個需要默認構造函數,一個不需要?

分析源碼

java原生序列化中反序列化時,是如何創建對象的 這篇文檔中有貼源碼,我這里就不貼源碼了:

  • 假如,反序列化的目標類實現了 Externalizable 接口,那么就通過反射得到就是該目標類對應的默認構造函數:
// cl 在這里就是我們的 User.class,這段代碼在 ObjectStreamClass 的靜態方法 getExternalizableConstructor 中
Constructor<?> cons = cl.getDeclaredConstructor((Class<?>[]) null);
  • 假如,反序列化的目標類實現了 Serializable 接口,那么就會獲取 Object 的默認構造函數
// 這段代碼是從 ObjectStreamClass 的靜態方法 getSerializableConstructor 中節選的,它會一直調用 getSuperClass 找父類
// 最終找到了 Object,並且獲取了 Object 的默認構造函數
Class<?> initCl = cl;
while (Serializable.class.isAssignableFrom(initCl)) {
    Class<?> prev = initCl;
    if ((initCl = initCl.getSuperclass()) == null ||
        (!disableSerialConstructorChecks && !superHasAccessibleConstructor(prev))) {
        return null;
    }
}
Constructor<?> cons = initCl.getDeclaredConstructor((Class<?>[]) null);
...

獲取到了構造函數對象 Constructor,就可以通過反射創建 Java 對象了:

// 實例化的調用
cons.newInstance();

但是有一個比較納悶的問題,那就是用 Object 的構造函數為啥還能創建子類對象??
我們可以看一下 Constructor#newInstance 方法:

public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    ...
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

實例化,還是要靠 ConstructorAccessor 實例。而此時該對象實例,是在 MethodAccessorGenerator#generateSerializationConstructor 方法中通過 asm 操作字節碼返回的全新SerializationConstructorAccessorImpl 對象,通過這個對象就可以創建我們想要的 User 對象了。但是這種方式反序列化創建對象時,是不會調用我們源代碼 User 中的默認構造函數的。

附錄

引入工具包

序列化的方法,在 Apache 中已經有了實現,參考 org.apache.commons.lang3.SerializationUtils 。你們可以使用以下 maven 配置引入依賴:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.11</version>
</dependency>

序列化工具類 SerializationUtils

下面這個類來自於 org.apache.commons.lang.SerializationUtils,是序列化的工具類。如果不想導入包,可以直接用這段代碼:

public class SerializationUtils {
    public SerializationUtils() {
    }

    public static Object clone(Serializable object) {
        return deserialize(serialize(object));
    }

    public static void serialize(Serializable obj, OutputStream outputStream) {
        if (outputStream == null) {
            throw new IllegalArgumentException("The OutputStream must not be null");
        } else {
            ObjectOutputStream out = null;

            try {
                out = new ObjectOutputStream(outputStream);
                out.writeObject(obj);
            } catch (IOException var11) {
                throw new SerializationException(var11);
            } finally {
                try {
                    if (out != null) {
                        out.close();
                    }
                } catch (IOException var10) {
                }

            }

        }
    }

    public static byte[] serialize(Serializable obj) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
        serialize(obj, baos);
        return baos.toByteArray();
    }

    public static Object deserialize(InputStream inputStream) {
        if (inputStream == null) {
            throw new IllegalArgumentException("The InputStream must not be null");
        } else {
            ObjectInputStream in = null;

            Object var2;
            try {
                in = new ObjectInputStream(inputStream);
                var2 = in.readObject();
            } catch (ClassNotFoundException var12) {
                throw new SerializationException(var12);
            } catch (IOException var13) {
                throw new SerializationException(var13);
            } finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                } catch (IOException var11) {
                }

            }

            return var2;
        }
    }

    public static Object deserialize(byte[] objectData) {
        if (objectData == null) {
            throw new IllegalArgumentException("The byte[] must not be null");
        } else {
            ByteArrayInputStream bais = new ByteArrayInputStream(objectData);
            return deserialize((InputStream)bais);
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM