MENU

序列化与反序列化(三)自定义序列化

July 3, 2018 • Code

前言:本文通过源码对自定义序列化的过程作深入理解。

在 Java 中自定义序列的方式有两种:

  1. 实现 Serializable 接口 + (writeObject 方法 & readObject 方法)。
  2. 实现 Externalizable 接口,实现其中方法 readExternalwriteExternal

第二种方法在 序列化与反序列化(二)深入序列化 - transient 与 Externalizable 中已有栗子,故本文着重于探讨第一种,writeObjectreadObject 方法的使用,并从源码方面分析这两个方法何时被调用

自定义序列化

栗子

  • 实现了 Serializable 接口及 writeObjectreadObject 方法的实体类 User
public class User implements Serializable {

    private static final long serialVersionUID = 7564890678302496342L;
    private long userId;
    private String userName;

    public User(long userId, String userName) {
        this.userId = userId;
        this.userName = userName;
    }

    private void writeObject(ObjectOutputStream stream) throws IOException, ClassNotFoundException {
        // 直接调用了默认的序列化方法,也可以自己手动实现
        stream.defaultWriteObject();
        System.out.println("------------ 自定义 writeObject ------------");
    }

    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
        // 直接调用了默认的序列化方法,也可以自己手动实现
        stream.defaultReadObject();
        System.out.println("------------ 自定义 readObject ------------");
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                '}';
    }
}
  • 执行序列化的类 CustomSerial
public class CustomSerial {

    public static void main(String[] args) {
        // ------------ 1. 序列化 ------------
        User u = new User(1L, "kbrx93");
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("User.ser"))) {
            oos.writeObject(u);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // ------------ 2. 反序列化 ------------
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("User.ser"))) {
            u = (User) ois.readObject();
            System.out.println(u);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 结果

从上图可看出:实现 Serializable 接口的类如果存在上述两个方法,在序列化与反序列的时候的确会调用它们进行序列化操作

源码

下面通过分析对象输入输出流的源码来寻找上面两个方法的调用过程。

方法调用链

具体源码

  • writeObject 方法
public final void writeObject(Object obj) throws IOException {
    
    ...
    ...
    
    try {
        writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0) {
            writeFatalException(ex);
        }
        throw ex;
    }
}
  • writeObject0 方法
private void writeObject0(Object obj, boolean unshared)
    throws IOException
{
    ...
    try {
        // handle previously written and non-replaceable objects
        ...
        ...

        // check for replacement object
        Object orig = obj;
        Class<?> cl = obj.getClass();
        ObjectStreamClass desc;
        for (;;) {
            // REMIND: skip this check for strings/arrays?
            Class<?> repCl;
            desc = ObjectStreamClass.lookup(cl, true);
            if (!desc.hasWriteReplaceMethod() ||
                (obj = desc.invokeWriteReplace(obj)) == null ||
                (repCl = obj.getClass()) == cl)
            {
                break;
            }
            cl = repCl;
        }
        
        ...
        ...

        // if object replaced, run through original checks a second time
        ...
        ...

        // remaining cases
        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) {
            writeOrdinaryObject(obj, desc, unshared);
        } else {
            if (extendedDebugInfo) {
                throw new NotSerializableException(
                    cl.getName() + "\n" + debugInfoStack.toString());
            } else {
                throw new NotSerializableException(cl.getName());
            }
        }
    } finally {
        ...
    }
}

通过上面的代码也可看出序列化实际上是通过 instanceof 判断是否实现 Serializable 来实现,因此 Serializable 接口是空接口。 除此之外的类型还有字符串、数组、枚举,如果不是其中之一,则抛出 NotSerializableException 异常。

  • 在 writeObject0 方法中调用了 writeOrdinaryObject 方法。
private void writeOrdinaryObject(Object obj,
                                 ObjectStreamClass desc,
                                 boolean unshared)
    throws IOException
{
    ...
    ...

    try {
        ...

        if (desc.isExternalizable() && !desc.isProxy()) {
            writeExternalData((Externalizable) obj);
        } else {
            // 调用此方法
            writeSerialData(obj, desc);
        }
    } finally {
        ...
    }
}
  • writeSerialData 方法
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;
        if (slotDesc.hasWriteObjectMethod()) {
            ...
            ...
            try {
                curContext = new SerialCallbackContext(obj, slotDesc);
                bout.setBlockDataMode(true);
                // 反射调用 User 类中的 writeObject 方法
                slotDesc.invokeWriteObject(obj, this);
                bout.setBlockDataMode(false);
                bout.writeByte(TC_ENDBLOCKDATA);
            } finally {
                ...
            }

            curPut = oldPut;
        } else {
            defaultWriteFields(obj, slotDesc);
        }
    }
}

在 writeSerialData 方法 方法中最终调用 slotDesc.invokeWriteObject(obj, this)通过反射得到并执行 User 类中的 writeObject 方法

  • invokeWriteObject 方法
/**
 * Invokes the writeObject method of the represented serializable class.
 * Throws UnsupportedOperationException if this class descriptor is not
 * associated with a class, or if the class is externalizable,
 * non-serializable or does not define writeObject.
 */
void invokeWriteObject(Object obj, ObjectOutputStream out)
    throws IOException, UnsupportedOperationException
{
    ...

    if (writeObjectMethod != null) {
        try {
            writeObjectMethod.invoke(obj, new Object[]{ out });
        } catch (InvocationTargetException ex) {
            ...

        } catch (IllegalAccessException ex) {
            ...
        }
    } else {
        throw new UnsupportedOperationException();
    }
}

Javadoc 中写的十分清楚:调用 serializable 对象的 writeObject 方法。

ArrayList 自定义序列化应用
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }
    ...
}


private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in capacity
    s.readInt(); // ignored

    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        int capacity = calculateCapacity(elementData, size);
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}

ArrayList 集合类实现了 Serializable 接口,同时添加了 readObjectwriteObject 方法自定义序列化。主要原因是 ArrayList 内部存储形式是对象数组,当数组内大量元素是 null 时,默认序列化方式依旧序列化全部元素,效率过低

扩展:寻找指定 writeObject 与 readObject 方法名代码

调试到 invokeWriteObject 方法中时,可以在调试控制台看到当前 this 对象中已有 writeObject 方法指向。

回到 writeSerialData 方法中,可得下面语句调用了 invokeWriteObject 方法,所以 this 为 slotDesc。

slotDesc.invokeWriteObject(obj, this);

slotDesc 为 slots 数组中元素,而 slots 数组又由传入的 desc 得到。

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;
        ...
    }
}

继续往上追溯调用链,发现 desc 参数是在 writeObject0 方法中通过执行 desc = ObjectStreamClass.lookup(cl, true) 得到:

private void writeObject0(Object obj, boolean unshared)
    throws IOException
{
    ...
    try {
        // handle previously written and non-replaceable objects
        ...

        // check for replacement object
        Object orig = obj;
        Class<?> cl = obj.getClass();
        ObjectStreamClass desc;
        for (;;) {
            // REMIND: skip this check for strings/arrays?
            Class<?> repCl;
            desc = ObjectStreamClass.lookup(cl, true);
            ...
        }
        ...
        // if object replaced, run through original checks a second time
        ...
        
        // remaining cases
        ...
        ...
    } finally {
        depth--;
        bout.setBlockDataMode(oldMode);
    }
}

lookup 方法中返回值代码如下:

entry = new ObjectStreamClass(cl);
...
return entry

ObjectStreamClass 构造方法中有如下语句:

writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);

最终明确是在 ObjectStreamClass 类构造方法中指定了 writeObject 及 readObject 方法名。

小结

主要内容:

  • 自定义序列化使用方式
  • 自定义序列化源码分析
    • 调用链
    • 具体方法体
    • ArrayList 中应用自定义序列意义
    • 寻找指定自定义序列化的 writeObject 与 readObject 方法名位置

除此之外也可在源码中看到 Serializalbe 接口序列化功能实现:通过 instanceof 判断是否是字符串、数组、枚举或实现了 Serializable 接口,如果都不是,则抛出 NotSerializableException 异常。

参考

Last Modified: July 7, 2018
Archives QR Code
QR Code for this page
Tipping QR Code
Leave a Comment