public void writeFieldBegin(TField field) throws TException {
this.writeByte(field.type);
this.writeI16(field.id);
}
从上面的代码中可以看出序列化的过程中写入的是域的类型以及域的数字序号,从org.apache.thrift.protocol.TType中,我们也可以知道在thrift IDL支持的数据类型,如下所示
public final class TType {
public static final byte STOP = 0;
public static final byte VOID = 1;
public static final byte BOOL = 2;
public static final byte BYTE = 3;
public static final byte DOUBLE = 4;
public static final byte I16 = 6;
public static final byte I32 = 8;
public static final byte I64 = 10;
public static final byte STRING = 11;
public static final byte STRUCT = 12;
public static final byte MAP = 13;
public static final byte SET = 14;
public static final byte LIST = 15;
public static final byte ENUM = 16;
public TType() {
}
其中STOP用于序列化完所有的域后,写入序死化文件,表示所有的域都序列化完成,接下来是oprot.writeI32(struct.key);这条语句就是写入要序列化的int类型值,对应TBinaryProtocol的实现如下所示:
public void writeI32(int i32) throws TException {
this.i32out[0] = (byte)(255 & i32 >> 24);
this.i32out[1] = (byte)(255 & i32 >> 16);
this.i32out[2] = (byte)(255 & i32 >> 8);
this.i32out[3] = (byte)(255 & i32);
this.trans_.write(this.i32out, 0, 4);
}
大致意思就是将int转换为byte数组,写入下层的channel中,接下来就是oprot.writeFieldEnd();对应TBinaryProtocol的实现如下所示:
public voidwriteFieldEnd() {
}
接下来的这段代应就是序列化Test.thrift中定义的value,和上面的序列化过程基本类似,但是也有区别,在序列化string类型时,会先在序死化文件里写入字符串的长度,然后再写入字符串的值
if (struct.value != null) {
oprot.writeFieldBegin(VALUE_FIELD_DESC);
oprot.writeString(struct.value);
oprot.writeFieldEnd();
}
最后,会向序列化的文件里面写入一个字节的0表示序列化结束,如下所示
public void writeFieldStop() throws TException {
this.writeByte((byte)0);
}
从上面的序列化过程中,我们可以知道序列化后的文件里面只有域的类型以及域的数字序号,没有域的名称,因此与JSON/XML这种序列化工具相比,thrift序列化后生成的文件体积要小很多
有了序列化的生成过程,再来看看thrift是如何反序列化,就非常简单了,反序列化的代码如下所示
public void read(org.apache.thrift.protocol.TProtocol iprot, Test struct) throws org.apache.thrift.TException {
org.apache.thrift.protocol.TField schemeField;
iprot.readStructBegin();
while (true)
{
schemeField = iprot.readFieldBegin();
if (schemeField.type == org.apache.thrift.protocol.TType.STOP) {
break;
}
switch (schemeField.id) {
case 1: // KEY
if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
struct.key = iprot.readI32();
struct.setKeyIsSet(true);
} else {
org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
}
break;
case 2: // VALUE
if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
struct.value = iprot.readString();
struct.setValueIsSet(true);
} else {
org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
}
break;
default:
org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
}
iprot.readFieldEnd();
}
iprot.readStructEnd();
// check for required fields of primitive type, which can't be checked in the validate method
if (!struct.isSetKey()) {
throw new org.apache.thrift.protocol.TProtocolException("Required field 'key' was not found in serialized data! Struct: " + toString());
}
struct.validate();
}
反序列化最为核心的代码在while循环这里,schemeField是由域的类型type及域的数字序号id构成的一个类,如下所示
public class TField {
public final String name;
public final byte type;
public final short id;
public TField() {
this("", (byte)0, (short)0);
}
public TField(String n, byte t, short i) {
this.name = n;
this.type = t;
this.id = i;
}
public String toString() {
return "";
}
public boolean equals(TField otherField) {
return this.type == otherField.type && this.id == otherField.id;
}
}
iprot.readFieldBegin();就是从序列化文件中构造一个TField类型的对象,TBinaryProtocol的实现如下所示,从下面的源代码可以看出,首先读取域的类型,然后读取域的数字序号
public TField readFieldBegin() throws TException {
byte type = this.readByte();
short id = type == 0?0:this.readI16();
return new TField("", type, id);
}
构造完了TFiled对象之后,我们需要读取域的值,看switch语句,也很容易理解,要读取域的值,需要两个前提
1.域的数字序号相同
2.域的类型相同
在满足上面的两个要求的前提下,再根据域的类型,调用相应的读取方法,如果域的数字序号相同,但是域的类型不同,则会跳过给该域赋值,执行的代码逻辑是
org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
最后,反序列化完成后,还要需检查一下必传的值是否已经传了,调用下面这段代码
struct.validate();
由反序列化的过程,可以知道,Thrift的反序列化,没有用到java的反射技术,也没有开设过多的内存空间,因此同JSON/XML相比,反序列化更快,更省内存,从反序列化的过程中,我们可以看到
Thrift的向后兼容性,需要满足一定的条件
1.域的数字序号不能改变
2.域的类型不能改变
满足了上面的两点,无论你增加还是删除域,都可以实现向后兼容,勿需担心