概诉
上一节,我们已经详解了IDL详细的语法,Thrift 就是根据这个语法,替我们生成目标代码的。下面我们来具体分析一下,生成的代码究竟是什么作用。
准备
目录结构
我们来看一下代码结构1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20├── java
│ └── learn
│ └── thrift
│ ├── bean
│ ├── client
│ │ └── Client.java
│ ├── constant
│ │ └── ServerConfig.java
│ ├── gen_code.sh
│ ├── idl
│ │ ├── bean.thrift
│ │ └── hello.thrift
│ ├── idlcode
│ │ ├── HelloWorldService.java
│ │ └── UserService.java
│ └── server
│ ├── Server.java
│ └── handler
│ ├── HelloWorldServiceImpl.java
│ └── UserServiceImpl.java
代码生成
我们来看一下 gen_code.sh 的代码1
2
3
4
5
6
7
8
9
10
thrift_name=hello.thrift
thrift_bean=bean.thrift
pathroot=../..
echo "gen code "
thrift --gen java -out $pathroot ./idl/$thrift_name
thrift --gen java -out $pathroot ./idl/$thrift_bean
echo "finsh"
当我们执行这个脚本的时候 会将 idl 的两个文件生成对应的代码。
来看一下 bean.thrift 文件,这里主要是定义的 thrift struct 文件。1
2
3
4
5
6
7// namesapce 的用法 java 标识生成java 代码。 后边的标识报名,代码将会生成到那个包下。
// 就是上文提到的 bean 目录下。
namespace java learn.thrift.bean
struct Friends {
1: required i16 No
}
我们执行后发现在bean 下多了一个文件叫Friends 的类。
1 |
|
生成代码样例
1 | package learn.thrift.bean; |
代码详解
类图
下面我们会对上文的代码做详细分析。首先我们来看一下类图。
我们可以看到 Friends 是继承 TBase , TBase 又实现 TFieldIdEnum。
我们来看TBase 代码
基础数据结构
TBase
1 | public interface TBase<T extends TBase<?, ?>, F extends TFieldIdEnum> extends Comparable<T>, Serializable { |
TBase 是 Thrift 的所有Type 类型的基类接口,其中定了几个比较重要的方法,比如说读写,克隆和清空。 而TFieldIdEnum 只是一个id 和名字的枚举。 从这里可以看到,实现 TBase的类具有可序列化的功能。
TStruct
接下来是 本 Struct 的一个描述, TStruct 只是对 struct 名字的一个描述的封装类,并没有什么特别。
1 | private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Friends"); |
TStruct 类似的文件是 Thrift 协议层的一个定义,另外协议层还有一下方法
协议层
这一层主要是用来定义如何序列化数据,属于协议层的一部分,将会在其他章节详细讨论,本节不在详细讲述。
- TProtocol
- TBinaryProtocol
- TCompactProtocol
- TJSONProtocol
- TProtocolFactory
- TProtocolUtil
- TSimpleJSONProtocol
- TTupleProtocol
- TProtocolException
元数据
这里定义的各种其他元数据的类声明
- TBase64Utils : Base64 数据的编解码工具
- TField: Field 字段的声明
- TList : List 结构的声明
- TMap : Map 结构的声明
- TMessage : Message 结构的声明
- TMessageType: Message类型
- TSet : set类型的声明
- TStruct: struct 类型的声明
- TType : thrift 所有支持类型的枚举
TType
Thrift 所有支持的类型 都在 TType 里定义的,我们看一下TType的代码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public 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;
}
TMessage
这个是 封装结构元数据的上层类, 当我们调用RPC 的时候, Thrift 会将我们要求我们按照他的格式封装元数据,然后在通过TMessage 封装元数据,然后通过网络发送出去。1
2
3
4
5
6public final class TMessageType {
public static final byte CALL = 1;
public static final byte REPLY = 2;
public static final byte EXCEPTION = 3;
public static final byte ONEWAY = 4;
}
- TMessage 结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public final class TMessage {
public final String name;
public final byte type;
public final int seqid;
public TMessage() {
this("", (byte)0, 0);
}
public TMessage(String n, byte t, int s) {
this.name = n;
this.type = t;
this.seqid = s;
}
public String toString() {
return "<TMessage name:'" + this.name + "' type: " + this.type + " seqid:" + this.seqid + ">";
}
public boolean equals(Object other) {
return other instanceof TMessage ? this.equals((TMessage)other) : false;
}
public boolean equals(TMessage other) {
return this.name.equals(other.name) && this.type == other.type && this.seqid == other.seqid;
}
}TMap 结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public final class TMap {
public final byte keyType;
public final byte valueType;
public final int size;
public TMap() {
this((byte)0, (byte)0, 0);
}
public TMap(byte k, byte v, int s) {
this.keyType = k;
this.valueType = v;
this.size = s;
}
}TSet
1 | public final class TSet { |
TList
1 | public final class TList { |
TField
1 | public class TField { |
Friends 中每个变量都会用 TField 来封装,如:1
2private static final TField NO_FIELD_DESC =
new TField("No", TType.I16, (short)1);
里面记录了 Field 的名字,类型,和FieldId , thrift 会给每一个Struct的成员变量增加一个Field封装类。
IScheme
IScheme 的作用
除了变量的定义之外 Friends 类当中还有一个概念 叫 Scheme1
2
3
4
5private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
static {
schemes.put(StandardScheme.class, new FriendsStandardSchemeFactory());
schemes.put(TupleScheme.class, new FriendsTupleSchemeFactory());
}
看一下继承关系

FriendsStandardSchemeFactory 就是 Scheme 的工厂,意思就是新建一个 FriendsStandardScheme。 而 FriendsStandardScheme 又去实现了 IScheme 这个接口。 从而实现了 read 和 write 方法。
这个 Scheme 是干什么呢, 和 FriendsStandardScheme, Friends 又是什么关系呢?
我们知道,Friends 是继承 TBase 的 TBase 中有两个重要的方法1
2void read(TProtocol var1) throws TException;
void write(TProtocol var1) throws TException;
而 Friends 就是要实现这两个方法,1
2
3
4
5
6
7public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
schemes.get(iprot.getScheme()).getScheme().read(iprot, this);
}
public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
}
Scheme其实就是一种序列化方式,标识该类的具体读写策略、Friends序列化的时候,需要传递协议,也就是TProtocol的实现类,TProtocol会根据自身支持的 序列化方式选择来选择Friends所支持的序列化方式,也就是的对应的具体Scheme工厂。从而创建出具体的Scheme实现类。上述代码中
schemes.get(oprot.getScheme()).getScheme(),第一个oprot.getScheme()是选择对应的Scheme方式,也就是StandardScheme还是TupleScheme。 从Friends静态对象 schemes 中找到Scheme工厂然后实例化具体的Scheme的实现类。
本例中,Friends 一共实现类两种实现类 一种是 FriendsStandardScheme, 一种是·FriendsTupleScheme
来看代码1
2
3
4
5
6
7
8
9
10
11private static class FriendsTupleSchemeFactory implements SchemeFactory {
public FriendsTupleScheme getScheme() {
return new FriendsTupleScheme();
}
}
private static class FriendsStandardSchemeFactory implements SchemeFactory {
public FriendsStandardScheme getScheme() {
return new FriendsStandardScheme();
}
}
IScheme 具体实现方式。
下面我们具体来分析一下 FriendsStandardScheme 和 FriendsTupleSchemeFactory 具体的读写策略。
FriendsStandardScheme
我们来具体分析一下 FriendsStandardScheme1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45private static class FriendsStandardScheme extends StandardScheme<Friends> {
public void read(org.apache.thrift.protocol.TProtocol iprot, Friends 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: // NO
if (schemeField.type == org.apache.thrift.protocol.TType.I16) {
struct.No = iprot.readI16();
struct.setNoIsSet(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.isSetNo()) {
throw new org.apache.thrift.protocol.TProtocolException("Required field 'No' was not found in serialized data! Struct: " + toString());
}
struct.validate();
}
public void write(org.apache.thrift.protocol.TProtocol oprot, Friends struct) throws org.apache.thrift.TException {
struct.validate();
oprot.writeStructBegin(STRUCT_DESC);
oprot.writeFieldBegin(NO_FIELD_DESC);
oprot.writeI16(struct.No);
oprot.writeFieldEnd();
oprot.writeFieldStop();
oprot.writeStructEnd();
}
}
Write 操作
从代码来分析 write 方法是写入规则,首先写入了STRUCT_DESC描述, 然后写入了NO_FIELD_DESC就是 No 这个字段,然后根据No 的大小吸入了No的具体的值,因为 No 是i16的类型,所以调用了writeI16的方法,然后writeFieldEnd标识该字段结束。
读操作
读取操作其实很写入操作相反, 需要注意一点是 读操作有个标记为 为schemeField.type == org.apache.thrift.protocol.TType.STOP
遇到这个标记为,则读取直接停止。那么这个STOP是什么作用呢?
STOP 是 writeFieldStop 结束的标记为,标记该 Struct 的所有 Field 都已经读取完毕

从图中我们可以看到, 当Field 写入完毕后 wirte 会写入 TType.STOP 标记,代表所有的Field 都已经图区完毕,这个时候 read 就可以退出了。
而在每个Field,Struct 协议的最后,又会写入结束的标记。
这个标记是由协议来实现的,目前不同的协议有不同的写入方法
例如1
2
3
4
5
6
7// TBinaryProtocol.java 为空实现,什么也不写
public void writeFieldEnd() {}
// TJSONProtocol.java 协议实现方式
public void writeFieldEnd() throws TException {
this.writeJSONObjectEnd();// 这里输入的是 “}”
}
这里有个疑问,为什么 TBinaryProtocol 什么也不写呢?
问知道 TField 中有个 type 的字段, type 字段标明了 value 类型所占的长度,
所有我们用 往后读 type 所占的长度就可以获取 value 的值,所以 Field 的结束标记位可以什么都不用写。
那么又有读者会问,那么 String 类型呢,他没有固定长度。
二级制协议 TBinaryProtocol 中关于 写 String 写入的代码如下:1
2
3
4
5
6
7
8
9public void writeString(String str) throws TException {
try {
byte[] dat = str.getBytes("UTF-8");
this.writeI32(dat.length);
this.trans_.write(dat, 0, dat.length);
} catch (UnsupportedEncodingException var3) {
throw new TException("JVM DOES NOT SUPPORT UTF-8");
}
}
从这里我们可以看出, 写 String string 转成UTF8的编码,然后在value的地方前两个字节写入字符串的长度,然后在写入字符创的 byte 数据。
所以说,对于变长类型,写入的规则为 长度 + 数据的方式。
FriendsTupleScheme
FriendsTupleScheme 重新定义了一种名为 TTupleProtocol 的协议,这个协议是一个TCompactProtocol 的子类, TCompactProtocol 我会在协议层分析。
简单说 FriendsTupleScheme 一种压缩的编码方式,将所有的指以固定长度压缩在一起。结构如下
1 | private static class FriendsTupleScheme extends TupleScheme<Friends> { |
GET SET 方法
SET 方法
Friends 为每一个Field 都生成了一个 set 方法 和一个 isSetXXX 的方法,标识该值是否被set。之所以有这个方式是因为 当我们在给 一个 i31的数 定义为 optional 的时候,i32 对应的是Java 语言的int 型 那么 这个值被初始化为 0,那么当rpc 过来的 这个值 就是0的时候,程序无法区分 这个0 是没有set 还是值就是0
注意,在设计RPC 协议或者其他协议的时候,避免 0 这个值。
Thrift 会给String 每个字段顶一个id, 名字为:__{filed name}_ISSET_ID,又会定义个bitSet 结构 bitSet 的大小为字段的个数。
当我们执行 setXX 这个方法的时候会调用 setXXIsSet(true) 方法。 来标明这个方法已经赋过值了。setXXIsSet(true) 本意就是讲 这个字段对应的BitSet 的位置控制成1。
1 | private static final int __NO_ISSET_ID = 0; |
同样我们还可以判断这个指是否1
2
3
4public boolean isSetNo() {
return __isset_bit_vector.get(__NO_ISSET_ID);
}
FieldMetaData 结构
Thrift 对于每个生成的类会有一个 FieldMetaData 的结构
这个类会和 Field 一个名称的枚举类型来对应成一个map结构。FieldMetaData 主要是封装了 Field 值的一些元数据。
1 | public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; |
首先我们来看一下meta包下都有哪些字段
- EnumMetaData
- FieldMetaData
- FieldValueMetaData
- ListMetaData
- MapMetaData
- SetMetaData
- StructMetaData
继承关系类图

FieldMetaData
这个数据结构是Friends 用来说明其中涉及Field信息的元数据。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class FieldMetaData implements java.io.Serializable {
public final String fieldName;
public final byte requirementType;
public final FieldValueMetaData valueMetaData;
private static Map<Class<? extends TBase>, Map<? extends TFieldIdEnum, FieldMetaData>> structMap;
// 所有Meta的映射关系
static {
structMap = new HashMap<Class<? extends TBase>, Map<? extends TFieldIdEnum, FieldMetaData>>();
}
public FieldMetaData(String name, byte req, FieldValueMetaData vMetaData){
this.fieldName = name; // Field 名字
this.requirementType = req; // 是否是必须的
this.valueMetaData = vMetaData; // value 字段的元数据
}
// ....
}Friends 或 new 一个这个出来,并传入了相应的信息,我们struct的中的required optional 的关键字 就是通过 这个常量类来定义的。1
2
3
4
5
6
7
8
9public final class TFieldRequirementType {
public static final byte REQUIRED = 1;
public static final byte OPTIONAL = 2;
public static final byte DEFAULT = 3;
public TFieldRequirementType() {
}
}
FieldValueMetaData
既然大部分是继承 FieldValueMetaData 那么我们首先来看这个数据结构,1
2
3
4
5
6
7public class FieldValueMetaData implements java.io.Serializable {
public final byte type; // Field 类型
private final boolean isTypedefType; // 是否是 Thrift 预定义的类型(TType 预定义的类型)
private final String typedefName; // Filed 名称
private final boolean isBinary; // 是否二进制协议
// ...
}
获取FiledMetaData
- 全局获取
当我们新建好了 FiledMetaData之后可以通过 FiledMetaData 静态类来获取,
FiledMetaData.getStructMetaDataMap(Friends.class)
- Friend API获取
1
2Friends friends = new Friends();
friends.getFieldValue(Friends._Fields.NO);
复杂符合类型
我们的 Friends 这是简单定义了 Friends 一个i16的字段No
如果我们在Friends 里增加一些复杂类型,Java 文件又是什么情况呢?
这里还要还有一个问题,我们只是分析了Struct 的生成类,那么Service的生成类是怎样的呢?
由于篇幅问题,我们这个话题留在下期去讨论。
总结
本节主要是总结了通过struct 文件生成的代码,主要谈论了框架生成的一些特性。更有一些协议层的相关的方法,thrift 的Java 代码非常轻量级,分层也非常明确,那么这些东西将会如何组织,整个的分层又是什么呢?我们下一节在详细论述。
赞赏一下