本文基于 Dubbo 2.6.1 版本,望知悉。
1. 概述
本文分享基于 Dubbo 自己实现的序列化拓展。要实现序列化的高性能,需要考虑两方面:
- 序列化和反序列化速度快
- 从传输角度,数据压缩效果好,即序列化后的数据量体积小
旁白君:从以下开始,Dubbo 指的是 Dubbo 序列化拓展,而不是 Dubbo PRC 框架。请注意。
在 《用户指南 —— 性能测试报告》 和 《在Dubbo中使用高效的Java序列化(Kryo和FST)》 中,我们可以看到,Dubbo 是一种相对优秀的实现方式。虽然,在最新版本的 Dubbo 项目中,dubbo-serialize
模块已经去除了 Dubbo 序列化的实现,猜测因为引入 Kryo 和 FST ,相比来说更优秀。
当然,即使如此,艿艿觉得了解下 Dubbo 序列化是如何实现的,是一种非常棒的眼界提升,特别是序列化的数据压缩,在很多场景下都会使用,例如 Lucene 的数据存储。
下面,我们跟着代码,一起愉快的玩耍把。本文涉及代码如下:
写的有点匆忙,也有点着急,如果有错误,或者不清晰的地方,请往死里抽(告诉)我。哈哈哈。
2. GenericDataFlags
Dubbo ,是一种有序、紧凑的序列化方式。如下是序列化后的二进制数据流的示意图:
- 不同于 JSON / XML 等序列化方式,无需序列化每个属性名。通过 Builder 对象,创建每个类的序列化和反序列化的具体代码。
- 这样,我们就避免了属性名的序列化,提升了速度,减少了数据的体积。
- 当然,反过来说,如果对象发生了变化( 增加或删除属性 ),可能会出现 Client 和 Server 序列化的不兼容,因为属性的顺序发生了变化。
- 属性值和属性值之间无间隔,通过属性值的标志位 Flag 保证,也就是本小节要分享的 GenericDataFlags 。
下面,我们来看看 com.alibaba.dubbo.common.serialize.support.dubbo.GenericDataFlags
,通用数据标记位枚举,代码如下:
public interface GenericDataFlags { |
😜 是不是有点一脸懵逼?!我们把枚举做一次规整,如下图所示:
- 在每个属性值( 即 field )的首个 Byte 位,称为标志位 Flag 。目前我们分成两大类( 图中,绿色部分 ):
- Varint ,变长数字,占用 Byte 值的
[0, 128)
区间。 - Object ,对象,占用 Byte 值的
[-128, 0)
区间。
- Varint ,变长数字,占用 Byte 值的
- 标志位 Flag 根据用途,可以分成两种类型(注意,值是不重叠的):
- Tag ,标签( 图中,橙色部分 )。
- 以 VarInt 举例子,数字分成 BYTE、SHORT、INT、LONG 四种数据类型。 通过标记位,表示数字占用多少 Byte ,从而实现变长,节省 Byte 的占用。例如,属性值类型为 Long ,但是值是 100L ,那么只需要要 1 Byte( 标记位为 VARINT8 ) + 1 Byte( 100L ) = 2 Byte 。
- 当然,这种方式也有缺点,对于大整数,会多占用一个标记位,例如
Integer.MAX_VALUE
。从统计上来说,业务系统更多的是小整数。所以,这个缺点也是能够接受的。
- CONSTANTS , 枚举( 图中,黄色部分 ),用于常用属性值。
- Tag ,标签( 图中,橙色部分 )。
可能有胖友会问,上面只提到了数字怎么序列化,那么对象怎么序列化呢?我们以 POJO 为例子,简单说下。实际上,我们可以把对象理解成一个属性值的集合,通过下面会看到的 Builder 类,生成该对象的序列化和反序列化的过程的代码即可。
当然,对象不仅仅有 POJO ,还有 MAP,数组等等,下面我们都会看到具体的处理代码。
🙂 嗯,哔哔了这么多,让我们愉快的开始看代码把。
3. Data
3.1 GenericDataOutput
com.alibaba.dubbo.common.serialize.support.dubbo.GenericDataOutput
,实现 DataOutput,GenericDataFlags 接口,Dubbo 数据输出实现类。
3.1.1 构造方法
/** |
mCharBuf
属性,序列化字符串的临时结果的 Buffer 数组,用于#writeUTF(String v)
方法中。#CHAR_BUF_SIZE
静态属性,默认大小。
mTemp
属性,序列化 Varint 的临时结果的 Buffer 数组,用于#writeVarint32(int)
和#writeVarint64(long)
方法中。- 数组大小为 9 ,因为 Varint 最大占用 9 字节,Tag( 1 Byte ) + Long( 8 Bytes ) 。
mBuffer
属性,序列化结果的 Buffer 数组。mPosition
属性,当前写入位置。mLimit
属性,容量大小。mOutput
属性,结果输出,mBuffer => mOutput
中。
3.1.2 writeBool
|
- 通过 1 表示 TRUE ,0 表示 FALSE 。占用 1 Byte ,使用 CONSTANTS( VARINT_1、VARINT_0 ) 即可。
调用
#write0(byte b)
方法,写入mBuffer
中。代码如下:protected void write0(byte b) throws IOException {
// 超过 mBuffer 容量上限,刷入 mOutput 中
if (mPosition == mLimit) {
flushBuffer();
}
// 写入 mBuffer 中。
mBuffer[mPosition++] = b;
}#flushBuffer()
方法,代码如下:
public void flushBuffer() throws IOException {
if (mPosition > 0) {
// 写入 mOutput
mOutput.write(mBuffer, 0, mPosition);
// 重置当前写入位置
mPosition = 0;
}
}
3.1.3 writeByte
1: |
- 第 4 行:// TODO 【8034】为什么没有负数的枚举
- 第 5 至 12 行:符合 Varint CONSTANTS ,写入对应的 CONSTANTS。
- 第 13 至 19 行:不符合 Varint CONSTANTS ,调用两次
#write0(byte b)
方法,写入 VARINT8 + 具体值v
。
3.1.4 writeShort
|
- 调用
#writeVarint32(int v)
方法,写入。代码如下:
1: private void writeVarint32(int v) throws IOException { |
- 第 5 至 12 行:符合 Varint CONSTANTS ,写入对应的 CONSTANTS。
第 11 至 43 行:不符合 Varint CONSTANTS ,写入 TAG + 具体值 。
- 第 16 至 22 行:顺序循环读取每个字节,存到
b
数组中。- 第 18 行:先
0xff
做%256
取余,获取到一个字节。再 (byte) 转换成 BYTE 值。因为,BYTE 数据范围为[-128, 127]
,所以取余的结果为[128, 255]
范围时,则会被高位截取,变成负数。例如,255 会变成 -1 。也因此,反序列化时,需要做一次0xff |
操作,来补齐高位的 1。 - 第 18 行:
b[++ix]
,先增加ix
的值,在写入b
数组中。因为,首 Byte 位为 TAG 。 - 第 19 至 21 行:无可读字节,结束循环。
- 第 18 行:先
- 第 24 至 29 行:最后一次取余,大于等于 128 时,在 (byte) 转换后,变成了负数,需要补一个 0 到
b
中,否则反序列化后会被误认为负数。例如:v = 255
。 第 30 至 37 行:负数使用补码表示,高位是大量的 1 ,需要循环去除。另外,LONG 的位数比 INT 更多,所以,相同数字,LONG 型会比 INT 型更多,例如
long v = -662L
和int v = -662
。示例如下:INT
-110 -3 -1 -1
LONG
-110 -3 -1 -1 -1 -1 -1 -1- x
- 涉及大量的位操作,不熟悉的胖友,请 Google 复习下大学课程。😈
- 第 40 行:写入 TAG ,到
b
的首位。 第 42 行:调用
#write0(byte[] b, int off, int le)
方法,批量写入mBuffer
中。代码如下:protected void write0(byte[] b, int off, int len) throws IOException {
int rem = mLimit - mPosition;
// 未超过 mBuffer 容量上限,批量写入 mBuffer 中
if (rem > len) {
System.arraycopy(b, off, mBuffer, mPosition, len);
mPosition += len;
} else {
// 部分批量写满 mBuffer 中
System.arraycopy(b, off, mBuffer, mPosition, rem);
mPosition = mLimit;
// 刷入 mOutput 中
flushBuffer();
off += rem; // 新的开始位置
len -= rem; // 新的长度
// 未超过 mBuffer 容量上限,批量写入 mBuffer 中
if (mLimit > len) {
System.arraycopy(b, off, mBuffer, 0, len);
mPosition = len;
// 超过 mBuffer 容量上限,批量写入 mOutput 中
} else {
mOutput.write(b, off, len);
}
}
}
- 第 16 至 22 行:顺序循环读取每个字节,存到
3.1.5 writeInt
|
3.1.6 writeLong
|
- 调用
#writeVarint64(long v)
方法,写入。代码如下:
1: private void writeVarint64(long v) throws IOException { |
- 第 2 至 5 行:当
v
数据范围在 INT 内时,调用#writeVarint32(int v)
处理。 - 第 7 至 30 行:当
v
数据范围在 LONG 内时, 不符合 Varint CONSTANTS ,写入 TAG + 具体值,和#writeVarint32(int v)
后半段的代码是一致的。
3.1.7 writeFloat
|
3.1.8 writeDouble
|
3.1.9 writeUInt
public void writeUInt(int v) throws IOException { |
- UInt ,Unsingned Int ,无符号整数( 正数 )。被用于表示字符串、数组的长度。序列化时,和上文我们看到的 Varint 一样,也是变长数字,但是方式不同。因为是正整数,所以可以使用 Byte 最高位的 1 ,原来用来表示负数,现在来表示是否有后续的 BYTE ,也是正整数的一部分。
- 🙂 代码已经添加注释,胖友自己看看哈。
- 推荐阅读 《数值压缩存储方法Varint》 ,里面 Varint 和 UInt 一样采用最高位来表示是否有后续数字,但是更加强大通用,使用 Zag 算法解决负数问题,在 Protobuf 中采用该方式。😈 文章中的 Varint 和本文我们看到的 Varint 不同。如果胖友对 Protobuf 的实现感兴趣,推荐阅读 《 Protocol Buffer 序列化原理大揭秘 - 为什么Protocol Buffer性能这么好?》 。
3.1.10 writeBytes
1: |
3.1.11 writeUTF
1: |
- 字符串和字节数组,二进制数据流的结构是一致的,差异点在字符串的每个字符,写入到
mBuffer
中,即【第 18 至 75 行】。 - 第 19 行:
-3
的原因,因为每个字符最多需要占用三个字节,事先无法得知。而【第 32 至 58 行】和【第 59 至 69 行】,逻辑上是一致的,相比来说【第 32 至 58 行】的#write0(byte b)
方法,多一个判断,考虑到性能,就分成了两段的逻辑,也就因此,多了这里的-3
。 - 第 22 至 25 行:循环读取字符到
buf
中。因为每次读取有CHAR_BUF_SIZE
最大限制,所以超过时,需要多次读取。读取完一批,处理完一批,不断重用buf
数组。 - 第 28 至 71 行:写入
buf
到mBuffer
中。因为 Java Character 的数据范围为[0, 65535]
,超过 BYTE 上限。所以写入每个字符时,分成三种情况:- 第 33 至 36 行:
[0, 128)
,占用一个字符。取七位,2 的 七次方为 128 ,从而满足数据范围。 - 第 37 至 45 行:
[128, 2048)
,占用两个字符。取六位、七位,2 的十三次方为 2048 ,从而满足数据范围。 - 第 46 至 58 行:
[2048, 65536)
,占用三个字符。取五位、七位、七位,2 的十九次方为 65536 ,从而满足数据范围。 - 为什么首位取的不同的位数呢?在反序列化时,可以根据首位数的高位来判断,到底完整的字符,占用了几个字节。🙂 或者,我们可以理解成,这是一种针对当前场景实现的变长整数。
- 第 33 至 36 行:
- 第 74 行:计算
buf
新的开始读取位置。
3.2 GenericDataInput
com.alibaba.dubbo.common.serialize.support.dubbo.GenericDataInput
,实现 DataInput,GenericDataFlags 接口,Dubbo 数据输入实现类。
3.2.1 构造方法
/** |
mBuffer
属性,读取 Buffer 数组。mRead
属性,当前读取位置。mPosition
属性,最大可读取位置。mInput
属性,输入流。
3.2.2 readBool
|
调用
#read0()
方法,读取字节。代码如下:protected byte read0() throws IOException {
// 读取到达上限,从 mInput 读取到 mBuffer 中。
if (mPosition == mRead) {
fillBuffer();
}
// 从 mBuffer 中,读取字节。
return mBuffer[mPosition++];
}#fillBuffer()
方法,代码如下:private void fillBuffer() throws IOException {
// 重置 mPosition
mPosition = 0;
// 读取 mInput 到 mBuffer 中
mRead = mInput.read(mBuffer);
// 未读取到,抛出 EOFException 异常
if (mRead == -1) {
mRead = 0;
throw new EOFException();
}
}
3.2.3 readByte
1: |
- 第 4 行:调用
#read0()
方法,读取字节。 - 第 6 至 8 行:不符合 Varint CONSTANTS ,调用
#read0()
方法,读取字节返回。 - 第 9 至 14 行:符合 Varint CONSTANTS,返回对应的值。
3.2.4 readShort
|
- 调用
#readVarint32()
方法,读取。代码如下:
1: private int readVarint32() throws IOException { |
- 第 3 行:调用
#read0()
方法,读取首位 Byte 字节。 - 第 6 至 30 行:不符合 Varint CONSTANTS,读取 TAG + 具体值。
& 0xff
操作,补回被截取最高的 1,从而恢复原数,对应#writeVarint32(int v)
方法的【第 18 位】。| 0xff000000
操作,补齐负数的高位,对应#writeVarint32(int v)
方法的【第 30 至 36 位】。
- 第 31 至 36 行:符合 Varint CONSTANTS,返回对应的值。
3.2.5 readInt
|
3.2.6 readLong
|
#readVarint64()
和#readVarint32()
基本一致,胖友自己查看。
3.2.7 readFloat
|
3.2.8 readDouble
|
3.2.9 readUInt
public int readUInt() throws IOException { |
3.2.10 readBytes
|
#read0(int len)
方法,批量读取字节。代码如下:protected byte[] read0(int len) throws IOException {
int rem = mRead - mPosition;
byte[] ret = new byte[len];
// 未超过 mBuffer 剩余可读取,批量写入 mBuffer 中。mBuffer => ret
if (len <= rem) {
System.arraycopy(mBuffer, mPosition, ret, 0, len);
mPosition += len;
} else {
// 部分批量写入 ref 中。mBuffer => ret
System.arraycopy(mBuffer, mPosition, ret, 0, rem);
mPosition = mRead;
len -= rem;
int read, pos = rem; // 新的 ret 读取起点
// mInput => ret
while (len > 0) {
read = mInput.read(ret, pos, len);
if (read == -1) {
throw new EOFException();
}
pos += read; // 新的 ret 读取起点
len -= read;
}
}
return ret;
}
3.2.11 readUTF
1: |
- 第 13 至 25 行:反序列化每个字符,通过首位数的高位来判断,到底完整的字符,占用了几个字节:
- 第 15 至 16 行:数据范围是
[0, 128)
,最大数 127 的二进制为01 11 11 11
,使用& 0x80
运算后,会等于 0 。 - 第 17 至 19 行:数据范围是
[128, 2048)
,因为首位数取六位,并且使用0xC0 |
运算后,所以二进制为11 XX XX XX
,使用& 0xE0
运算后,会等于0XC0
。- 【第 15 至 16 行】如果使用
& 0x80
运算, 不会等于 0 ,不符合要求。- 第 20 至 22 行:数据范围是
[2048, 65536)
,因为首位数取五位,并且使用0xE0 |
运算后,所以二进制为11 1X XX XX
,使用& 0xF0
运算后,会等于0xE0
。
- 第 20 至 22 行:数据范围是
- 【第 15 至 16 行】如果使用
& 0x80
运算, 不会等于 0 ,不符合要求。 - 【第 17 至 19 行】如果使用
& 0xE0
运算,不会等于0xC0
,不符合要求。
- 【第 15 至 16 行】如果使用
- 第 15 至 16 行:数据范围是
4. Object
4.1 GenericObjectOutput
com.alibaba.dubbo.common.serialize.support.dubbo.GenericObjectOutput
,实现 ObjectOutput 接口,继承 GenericObjectOutput 类,Dubbo 对象输出实现类。
4.1.1 构造方法
/** |
isAllowNonSerializable
属性,对象是否允许不实现 Serializable 接口。Dubbo 序列化无需强制实现 Serializable 接口。考虑到通用性,默认false
不允许。mMapper
属性,类描述匹配器。通过该匹配器,可以将类描述,转换成对应的描述编号,从而加速序列化的速度,减少体积,类似 Kryo 的注册。默认使用 Builder 的 DEFAULT_CLASS_DESCRIPTOR_MAPPER 实现类。mRefs
属性,循环引用集合,和 FastJSON 的 《循环引用》 上,概念是一致的。在 AbstractObjectBuilder 中,我们会看到循环引用的实现。
4.1.2 writeObject
1: |
- 【第一种】第 4 至 8 行:对象为 NULL ,写入 OBJECT_NULL 到
mBuffer
。 - 【第二种】第 9 至 12 行:对象为 Object 类型,写入 OBJECT_DUMMY 到
mBuffer
。 【第三种】第 13 至 30 行:对象非空,写入 类描述 + 对象 到
mBuffer
。第 15 行:调用
ReflectUtils#getDesc(c)
方法,获得类描述。代码如下:public static String getDesc(Class<?> c) {
StringBuilder ret = new StringBuilder();
// Array
while (c.isArray()) {
ret.append('[');
c = c.getComponentType();
}
// 基本类型
if (c.isPrimitive()) {
String t = c.getName();
if ("void".equals(t)) ret.append(JVM_VOID);
else if ("boolean".equals(t)) ret.append(JVM_BOOLEAN);
else if ("byte".equals(t)) ret.append(JVM_BYTE);
else if ("char".equals(t)) ret.append(JVM_CHAR);
else if ("double".equals(t)) ret.append(JVM_DOUBLE);
else if ("float".equals(t)) ret.append(JVM_FLOAT);
else if ("int".equals(t)) ret.append(JVM_INT);
else if ("long".equals(t)) ret.append(JVM_LONG);
else if ("short".equals(t)) ret.append(JVM_SHORT);
// 类
} else {
ret.append('L');
ret.append(c.getName().replace('.', '/'));
ret.append(';');
}
return ret.toString();
}- x
- 第 17 行:调用
ClassDescriptorMapper#getDescriptorIndex(desc)
方法,获得类描述编号。- 【不存在】第18 至 21 行:写入 OBJECT_DESC + 类描述(字符串) 到
mBuffer
中。 - 【已存在】第 22 至 26 行:写入 OBJECT_DESC_ID + 类描述编号(编号) 到
mBuffer
中。 - 🙂 很明显,第二种的性能和体积都更好。当然,需要保证 Server 和 Client 的类描述编号是一致的。大多数情况下,我们只注册常用的数据类型到 ClassDescriptorMapper 中。
- 【不存在】第18 至 21 行:写入 OBJECT_DESC + 类描述(字符串) 到
- 第 28 行:调用
Builder#register(Class<T> c, boolean isAllowNonSerializable)
方法,获得类对应的 Builder 对象。 - 第 30 行:调用
Builder#writeToT obj, GenericObjectOutput out)
方法,序列化对象到 GenericObjectOutput 中的输出流。为什么可以这么做?看完 「6. Builder」 的分享,胖友就会找到答案。🙂 卖个小关子。
4.1.3 addRef
/** |
4.1.4 getRef
/** |
4.2 GenericObjectInput
com.alibaba.dubbo.common.serialize.support.dubbo.GenericObjectInput
,实现 ObjectInput 接口,继承 GenericDataInput 类,Dubbo 对象输入实现类。
4.2.1 构造方法
/** |
4.2.2 readObject
1: |
- 第 30 行:调用
ReflectUtils#desc2class(desc)
方法,获得类。 - 第 33 行:调用
Builder#register(Class<T> c, boolean isAllowNonSerializable)
方法,获得类对应的 Builder 对象。 - 第 33 行:调用
Builder#parseFrom(GenericObjectInput)
方法,反序列化成对象返回。
4.2.3 addRef
/** |
4.2.4 getRef
/** |
4.2.5 skipAny
【TODO 8035】1、已经限制的大小,这块代码没用了啊?!
胖友可先无视这个方法。
5. ClassDescriptorMapper
com.alibaba.dubbo.common.serialize.support.dubbo.ClassDescriptorMapper
,类描述匹配器接口。方法如下:
// 根据类描述编号,获得类描述 |
5.1 DEFAULT_CLASS_DESCRIPTOR_MAPPER
DEFAULT_CLASS_DESCRIPTOR_MAPPER 是 Builder 的内部属性。
/** |
在 Builder 的 static 代码块,会初始化 mDescMap
属性,代码如下:
static { |
6. Builder
com.alibaba.dubbo.common.serialize.support.dubbo.Builder
,实现 GenericDataFlags 接口,对象序列化代码构建器抽象类。功能如下:
- 类的序列化和反序列化的抽象定义。
- 提供常用类( 例如 Integer 、Long 、Map 等等 )的 Builder 实现类。
- 基于 Javassist 自动实现自定义类( 例如 User 、Student 等等 )的 Builder 实现类。
🙂 大体的类结构,如下图所示:
6.1 抽象方法
/** |
三个抽象方法:
- 对应类
- 序列化
- 反序列化
6.2 register
/** |
- 代码比较易懂,胖友看下注释哈。比较奇怪的是
c.isInterface()
的判断,为什么使用GenericBuilder
对象。在 「6.4 GenericBuilder」 会看到答案。
6.3 常用数据类型的 Builder 实现
在 static 代码块,初始化了常用数据类型的 Builder 实现,代码如下图:
代码比较简单,胖友点击 链接 ,自己查看。这里我们就以 HashMap 的 Builder 举例子,代码如下:
register(HashMap.class, new Builder<HashMap>() { |
6.4 GenericBuilder
GenericBuilder
,实现 Builder 接口,通用 Object 的 Builder 对象。代码如下:
static final Builder<Object> GenericBuilder = new Builder<Object>() { |
适用于所有对象。为什么这么说呢?我们以 #writeTo(Object obj, GenericObjectOutput out)
方法,举例子。在该方法中,会调用 GenericObjectOutput#writeObject(obj)
方法,那么在这个过程中,会获得 obj
对象,真正的 Builder 对象,从而序列化。如下图所示:
6.5 SerializableBuilder
SerializableBuilder
属性,实现 Builder 接口,通用 Serializable 的 Builder 对象,使用 Java 原生序列化方式实现。目前使用在:
- Throwable 对象。
- 带有 transient 修饰符属性的 Serializable 实现类。
因为是广泛匹配,所以不适合调用 #register(Class<T> c, boolean isAllowNonSerializable)
方法,进行注册。而是在 #newObjectBuilder(Class<?> c)
方法,通过硬编码判断匹配返回。
实现代码如下:
static final Builder<Serializable> SerializableBuilder = new Builder<Serializable>() { |
6.6 AbstractObjectBuilder
AbstractObjectBuilder ,实现 Builder 接口,Builder 抽象类。主要实现了循环引用对象的支持。代码如下:
|
和 Builder 提供的三个抽象方法一一对应,AbstractObjectBuilder 也定义了三个抽象方法:
/**
* 创建 Builder 对应类的对象
*
* @param in GenericObjectInput 对象
* @return 对应类的对象
* @throws IOException 当 IO 发生异常时
*/
abstract protected T newInstance(GenericObjectInput in) throws IOException;
/**
* 序列化对象到 GenericObjectOutput 中的输出流。
*
* @param obj 对象
* @param out GenericObjectOutput 对象
* @throws IOException 当 IO 发生异常时
*/
abstract protected void writeObject(T obj, GenericObjectOutput out) throws IOException;
/**
* 反序列化 GenericObjectInput 到对象
*
* @param ret 对象。
* 该对象在 {@link #parseFrom(GenericObjectInput)} 中,调用 {@link #newInstance(GenericObjectInput)} 创建
* @param in GenericObjectInput 对象
* @throws IOException 当 IO 发生异常时
*/
abstract protected void readObject(T ret, GenericObjectInput in) throws IOException;
6.6.1 GenericArrayBuilder
GenericArrayBuilder
,实现 AbstractObjectBuilder 抽象类,通用数组( Array ) 的 Builder 对象。代码如下:
static final Builder<Object[]> GenericArrayBuilder = new AbstractObjectBuilder<Object[]>() { |
- 因为
GenericArrayBuilder
实现 AbstractObjectBuilder 抽象类,所以,若数组中有相等的元素,可以使用循环引用的功能,从而提升解析速度,降低体积。
6.6.2 其他子类
在 #newObjectBuilder(Class<?> c)
中,基于 Javassist 自动实现每个类的 Builder 类,实现的就是 AbstractObjectBuilder 抽象类。
6.5 newBuilder
private static <T> Builder<T> newBuilder(Class<T> c) { |
6.5.1 newObjectBuilder
#newObjectBuilder(Class<?> c)
,基于 Javassist 自动实现每个类的 Builder 类( 继承 AbstractObjectBuilder 抽象类 ),并创建对应的 Builder 对象。代码超级冗长,老艿艿已经添加好了详细的代码注释,胖友点击 链接 自己查看。
实现原理,简单的说,其实就是,循环类的每个属性,拼接对应的序列化和反序列化的过程的代码字符串,最终提交给 Javassist 生成类。
良心如我,如下是一个示例:
- Student 和 Info 类 :
package com.alibaba.dubbo.common.serialize.dubbo; |
6.5.2 newEnumBuilder
#newEnumBuilder(Class<?> c)
,基于 Javassist 自动实现每个类的 Builder 类( 继承 Builder 接口 ),并创建对应的 Builder 对象。代码比较易懂,老艿艿已经添加好了详细的代码注释,胖友点击 链接 自己查看。
实现原理,粗暴的说,序列化使用 enum#name()
方法,反序列化使用 Enum#valueOf(Class<T> enumType, String name)
方法。
6.5.3 newArrayBuilder
#newArrayBuilder(Class<?> c)
,基于 Javassist 自动实现每个类的 Builder 类( 继承 Builder 接口 ),并创建对应的 Builder 对象。代码比较易懂,老艿艿已经添加好了详细的代码注释,胖友点击 链接 自己查看。
实现原理,直接的说,循环数组的每个元素,拼接对应的序列化和反序列化的过程的代码字符串,最终提交给 Javassist 生成类。
🙂 比较有意思的是,多维数组的处理,例如 int[][][]
。胖友可以想想。实际,也是比较简单的。
666. 彩蛋
大四( 2012 )的时候,写了自己的序列化实现 Ludaima_Protobuf ,基于 Protobuf 的配置文件 proto
,读取,生成序列化和反序列化的静态类,基本零优化。
现在回头看了 Dubbo 序列化的实现,还是收益良多。美滋滋。