【源码剖析】序列化 底层实现原理

shadowLogo

在之前的博文中,本人介绍了 Java对象经 序列化 后,转换成的 内容

相信很多同学在上一篇博文中,仍对 生成的内容的 格式 抱有很多疑惑
那么,在本篇博文中,本人就来在源码角度,来带同学们了解下 对象序列化 的本质:

我们平时使用 序列化 机制,基本上都会是如下步骤:

调用代码:

首先,我们需要一个之后会被序列化的对象:

pojo对象:

package edu.youzg.about_serialize.pojo;

import java.io.Serializable;

/**
 * @Author: Youzg
 * @CreateTime: 2021-2-16 16:48
 * @Description: 带你深究Java的本质!
 */
public class Fan implements Serializable {
    private static final long serialVersionUID = 3439052454193760044L;

    private String name;

    private Integer age;

    private int likeNum;

    public Fan() {
    }

    public Fan(String name, Integer age, int likeNum) {
        this.name = name;
        this.age = age;
        this.likeNum = likeNum;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public int getLikeNum() {
        return likeNum;
    }

    public void setLikeNum(int likeNum) {
        this.likeNum = likeNum;
    }

    @Override
    public String toString() {
        return "Fan{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", likeNum=" + likeNum +
                '}';
    }

}

那么,要被序列化的pojo准备好了,
现在,本人来给出一个 序列化对象 的 小Demo:

序列化对象:

package edu.youzg.about_serialize.demo;

import edu.youzg.about_serialize.pojo.Fan;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class YouzgDemo {

    public static void main(String[] args) throws IOException {
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./prettyGril.txt"));
            Fan aFan = new Fan("prettyGril", 16, 666);
            oos.writeObject(aFan);
        } catch (Exception e) {
            // TODO: handle exception
        }
    }

}

那么,至于运行结果,由于在前一篇博文中展示过,本人就不再展示了


相信看完前一篇博文的同学,对于最后的输出结果的内容,仍然抱有很多疑惑
那么,接下来,本人就来讲解下输出格式 以及 序列化对象的原理:

序列化 的 本质:

在上面的代码中,我们可以看出:
序列化对象,只需调用两个方法:

new ObjectOutputStream();

oos.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 的 blkmode属性 置为 true
    bout.setBlockDataMode(true);
    if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
    } else {
        debugInfoStack = null;
    }
}

那么,本人再来展示下 上面方法中 BlockDataOutputStream类构造函数

BlockDataOutputStream类 ✨ 构造函数 源码:

private static final int MAX_BLOCK_SIZE = 1024;
private static final int MAX_HEADER_SIZE = 5;
private static final int CHAR_BUF_SIZE = 256;

private final byte[] buf = new byte[MAX_BLOCK_SIZE];
private final byte[] hbuf = new byte[MAX_HEADER_SIZE];
private final char[] cbuf = new char[CHAR_BUF_SIZE];

private boolean blkmode = false;

private int pos = 0;

private final OutputStream out;

private final DataOutputStream dout;

BlockDataOutputStream(OutputStream out) {
    this.out = out;
    dout = new DataOutputStream(this);
}

可以看到:

BlockDataOutputStream类 其实就是 封装后DataOutputStream类
并 提供了一些 缓冲区参数

我们也可以看到,当执行 ObjectOutputStream类的构造函数时,构造了一个 BlockDataOutputStream类的对象,
并在之后的代码中,
那么,为什么在这几行代码中,本人单独挑选

bout = new BlockDataOutputStream(out);

这行代码进行代码展示呢?

这是因为 在之后的 writeObject方法 的代码中,会主要使用到 bout对象 的方法


在创建完的对象之后,的构造函数 调用了 writeStreamHeader方法
那么,本人就来讲解下 writeStreamHeader方法 的源码:

ObjectOutputStream类✨writeStreamHeader方法 源码:

/**
 * Magic number that is written to the stream header.
 */
final static short STREAM_MAGIC = (short)0xaced;

/**
 * Version number that is written to the stream header.
 */
final static short STREAM_VERSION = 5;

protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);
    bout.writeShort(STREAM_VERSION);
}

根据 源码注释,我们能够知道:

STREAM_MAGIC 代表 序列化的标识
STREAM_MAGIC 代表 版本

可以看到:

其实这个方法的 具体实现流程 我们不用跟下去
我们只需要知道这个方法的作用是:发送一个头信息 即可


其实,在 ObjectOutputStream类构造函数 中,还有一个 值得注意的点:

enableOverride = false;

那么,构造函数 的基本流程 我们理解了
现在,本人来讲解下 writeObject方法 的源码:

ObjectOutputStream类✨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;
    }
}

根据上文对代码的分析,我们能够知道:

执行 writeObject方法,其实只会执行 writeObject0方法


那么,接下来,我们来看看 writeObject0方法具体实现步骤

ObjectOutputStream类✨writeObject0方法 源码:

private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
    boolean oldMode = bout.setBlockDataMode(false);
    depth++;
    try {
    	// 1⃣️
        // 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;
        }

		// 2⃣️
        // 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 (enableReplace) {
            Object rep = replaceObject(obj);
            if (rep != obj && rep != null) {
                cl = rep.getClass();
                desc = ObjectStreamClass.lookup(cl, true);
            }
            obj = rep;
        }

		// 3⃣️
        // 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;
            }
        }

		// 4⃣️
        // 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 {
        depth--;
        bout.setBlockDataMode(oldMode);
    }
}

这里的源码太长了
在本人看来,长的代码的可读性很差
因此,本人在源码中通过 序号注释,将这个方法拆分成了几部分

那么,本人现在就来分别讲解下这几部分的原理:

第一部分:

// 1⃣️
// 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;
}

从源码的注释中,我们能够知道:

这部分代码的 主要功能 是:
处理 以前编写的对象不可替换对象

绝大多数情况下,我们的代码,是不会进入这个代码块的
所以,这个代码块我们只要知道它的作用就可以了
敷衍


第二部分:

// 2⃣️
// 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 (enableReplace) {
    Object rep = replaceObject(obj);
    if (rep != obj && rep != null) {
        cl = rep.getClass();
        desc = ObjectStreamClass.lookup(cl, true);
    }
    obj = rep;
}

根据注释,我们能够知道:

这部分代码 的功能检查可替换对象
其实,我们来读一读源码中的if判断条件,也会发现:
这部分的代码块 也是 不会执行


第三部分:

// 3⃣️
// 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;
    }
}

根据 源码注释之前代码的讲解,我们能够知道:

这里的代码块的作用是 如果对象被替换,则第二次执行原始检查
但是,我们并没有在上面代码块中替换对象
因此,这部分代码块 是不会执行的


第四部分:

// 4⃣️
// 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());
    }
}

从注释中,我们可以得知:

这部分的代码块的作用是:处理剩余类型的对象

其实这里我们也能看出:

就是在处理 字符串数组枚举可序列化对象


由于我们一般序列化进行网络传输的基本上都是 可序列化对象
那么,本人就来着重讲解下 可序列化对象 的处理方法进行详细讲解:

可序列化对象 的处理:

writeOrdinaryObject 方法✨源码解析:

private void writeOrdinaryObject(Object obj,
                                 ObjectStreamClass desc,
                                 boolean unshared)
    throws IOException
{
    if (extendedDebugInfo) {
        debugInfoStack.push(
            (depth == 1 ? "root " : "") + "object (class \"" +
            obj.getClass().getName() + "\", " + obj.toString() + ")");
    }
    try {
    	// 检查 可序列化性
        desc.checkSerialize();

		// 先写入一个 “对象标志符”,表示当前传输的数据为 一个对象
        bout.writeByte(TC_OBJECT);
        // 写入 类元数据
        writeClassDesc(desc, false);
        handles.assign(unshared ? null : obj);
        if (desc.isExternalizable() && !desc.isProxy()) {
            writeExternalData((Externalizable) obj);
        } else {
        	// 将 序列化后的对象数据 写入
            writeSerialData(obj, desc);
        }
    } finally {
        if (extendedDebugInfo) {
            debugInfoStack.pop();
        }
    }
}

writeClassDesc 方法✨源码解析:

private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
    throws IOException
{
    int handle;
    if (desc == null) {
        writeNull();
    } else if (!unshared && (handle = handles.lookup(desc)) != -1) {
        writeHandle(handle);
    } else if (desc.isProxy()) {
        writeProxyDesc(desc, unshared);
    } else {
    	// 最终我们的逻辑,只会执行这个方法
        writeNonProxyDesc(desc, unshared);
    }
}

writeNonProxyDesc 方法✨源码解析:

/**
 * Writes class descriptor representing a standard (i.e., not a dynamic
 * proxy) class to stream.
 */
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
    throws IOException
{
	// 先写入 一个“类元标志符”
    bout.writeByte(TC_CLASSDESC);
    handles.assign(unshared ? null : desc);

    if (protocol == PROTOCOL_VERSION_1) {
        // do not invoke class descriptor write hook with old protocol
        desc.writeNonProxy(this);
    } else {
    	// 这个if循环,一般会执行else代码块中的代码
        writeClassDescriptor(desc);
    }

    Class<?> cl = desc.forClass();
    bout.setBlockDataMode(true);
    if (cl != null && isCustomSubclass()) {
        ReflectUtil.checkPackageAccess(cl);
    }
    annotateClass(cl);
    bout.setBlockDataMode(false);
    // 写入 一个“object描述块结束标志符”
    bout.writeByte(TC_ENDBLOCKDATA);

    writeClassDesc(desc.getSuperDesc(), false);
}

那么,本人再来展示下上面方法,最后调用的writeClassDescriptor方法的源码:

writeClassDescriptor 方法✨源码解析:

protected void writeClassDescriptor(ObjectStreamClass desc)
        throws IOException
{
    desc.writeNonProxy(this);
}

可以看到:

这个方法 其实就是包装调用的 writeNonProxy方法


writeNonProxy 方法✨源码解析:

/**
 * Writes non-proxy class descriptor information to given output stream.
 */
void writeNonProxy(ObjectOutputStream out) throws IOException {
	// 先写入 类名
    out.writeUTF(name);
	// 先写入 类的序列号
    out.writeLong(getSerialVersionUID());

	// 计算 类的特性(flags)
    byte flags = 0;
    if (externalizable) {
        flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
        int protocol = out.getProtocolVersion();
        if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
            flags |= ObjectStreamConstants.SC_BLOCK_DATA;
        }
    } else if (serializable) {
    	// 一般都会执行这里的代码
    	// 因为我们序列化的对象,一般都是具有serializable属性的
        flags |= ObjectStreamConstants.SC_SERIALIZABLE;
    }
    if (hasWriteObjectData) {
        flags |= ObjectStreamConstants.SC_WRITE_METHOD;
    }
    if (isEnum) {
        flags |= ObjectStreamConstants.SC_ENUM;
    }
    // 将 类的特性 写入,以便“反解析”的进行
    out.writeByte(flags);

	// 写入 对象的字段前,先写入 字段的数量,以便之后的“反解析”过程的进行
    out.writeShort(fields.length);
    for (int i = 0; i < fields.length; i++) {
        ObjectStreamField f = fields[i];
        // 写入 字段的类型码
        out.writeByte(f.getTypeCode());
        // 写入 字段的名字
        out.writeUTF(f.getName());

		// 如果是 对象/接口
		// 写入 表示对象的字符串
        if (!f.isPrimitive()) {
            out.writeTypeString(f.getTypeString());
        }
    }
}

根据本人在源码中的注释,我们也能看出;

这个方法的 作用 是:
写入目标对象的 类型信息

具体写入顺序为:

  1. 类名
  2. 类的序列号
  3. 类的特性
  4. 字段信息(字段的类型码 + 字段的名字)
  5. 表示对象的字符串(如果是 对象接口)

那么,上面代码中,本人提及的 字段状态码 是什么呢?

字段类型码:

我们可以从这个方法中,看到 类型类型码对应关系

/**
 * Creates an ObjectStreamField representing a field with the given name,
 * signature and unshared setting.
 */
ObjectStreamField(String name, String signature, boolean unshared) {
    if (name == null) {
        throw new NullPointerException();
    }
    this.name = name;
    this.signature = signature.intern();
    this.unshared = unshared;
    field = null;

    switch (signature.charAt(0)) {
        case 'Z': type = Boolean.TYPE; break;
        case 'B': type = Byte.TYPE; break;
        case 'C': type = Character.TYPE; break;
        case 'S': type = Short.TYPE; break;
        case 'I': type = Integer.TYPE; break;
        case 'J': type = Long.TYPE; break;
        case 'F': type = Float.TYPE; break;
        case 'D': type = Double.TYPE; break;
        case 'L':
        case '[': type = Object.class; break;
        default: throw new IllegalArgumentException("illegal signature");
    }
}

那么,本人来总结下:

类型 类型码
Boolean Z
Byte B
Character C
Short S
Integer I
Long J
Float F
Double D
数组 [
Object(对象/接口) L
其它 非法参数异常

writeSerialData 方法✨源码解析:

/**
 * Writes instance data for each serializable class of given object, from
 * superclass to subclass.
 */
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()) {	// 如果 目标对象的类 实现了 writeObject方法
            PutFieldImpl oldPut = curPut;
            curPut = null;
            SerialCallbackContext oldContext = curContext;

            if (extendedDebugInfo) {
                debugInfoStack.push(
                    "custom writeObject data (class \"" +
                    slotDesc.getName() + "\")");
            }
            try {
                curContext = new SerialCallbackContext(obj, slotDesc);
                bout.setBlockDataMode(true);
                slotDesc.invokeWriteObject(obj, this);
                bout.setBlockDataMode(false);
                bout.writeByte(TC_ENDBLOCKDATA);
            } finally {
                curContext.setUsed();
                curContext = oldContext;
                if (extendedDebugInfo) {
                    debugInfoStack.pop();
                }
            }

            curPut = oldPut;
        } else {
        	// 默认方式,写入实例数据
            defaultWriteFields(obj, slotDesc);
        }
    }
}

那么,本人再来展示下 默认写入实例数据源码

defaultWriteFields 方法✨源码解析:

private void defaultWriteFields(Object obj, ObjectStreamClass desc)
    throws IOException
{
    Class<?> cl = desc.forClass();
    if (cl != null && obj != null && !cl.isInstance(obj)) {
        throw new ClassCastException();
    }

    desc.checkDefaultSerialize();

    int primDataSize = desc.getPrimDataSize();
    if (primVals == null || primVals.length < primDataSize) {
        primVals = new byte[primDataSize];
    }
    // 获取 对象中的 基本类型的实例数据
    // 并将 这些数据 放入 primVals数组 中
    desc.getPrimFieldValues(obj, primVals);
    // 写入 实例数据数组
    bout.write(primVals, 0, primDataSize, false);

	// 将 目标对象的成员属性字段 保存在 objVals数组 中
    ObjectStreamField[] fields = desc.getFields(false);
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    desc.getObjFieldValues(obj, objVals);
    for (int i = 0; i < objVals.length; i++) {
        if (extendedDebugInfo) {
            debugInfoStack.push(
                "field (class \"" + desc.getName() + "\", name: \"" +
                fields[numPrimFields + i].getName() + "\", type: \"" +
                fields[numPrimFields + i].getType() + "\")");
        }
        try {
        	// 以 “递归式” 将 目标对象 的数据,转换并写入
            writeObject0(objVals[i],
                         fields[numPrimFields + i].isUnshared());
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }
}

从上面代码和本人的注释,我们能够得出:

这个方法的作用是:
实例对象成员属性,以“递归式”,进行 序列化写入


那么,本人最后,以一张图,来总结下 一个对象进行序列化 的全过程:

总结:

一个对象的 序列化顺序,是按照如下步骤
序列化流程

其实这还不算序列化的 全部实现原理
因为在我们学习序列化流的过程中,transient关键字静态成员属性 是无法被序列化的
因此,整个Java体系的 序列化流程,还是非常庞大的,本篇博文仅 介绍其 核心功能实现原理


那么,到这里,对象序列化底层实现原理就讲解完毕了!
无发可脱

参考文章:

《Java中序列化实现原理研究》

posted @ 2021-02-17 18:58  在下右转,有何贵干  阅读(407)  评论(0编辑  收藏  举报