Thrift框架详解(三)

IDL生成文件详解

Posted by Jason Lee on 2020-03-04

概诉

上一节,我们已经详解了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
#!/bin/zsh
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
2
3
4
5
6

├── java
│   └── learn
│   └── thrift
│   ├── bean
|-- Friends.java

生成代码样例

1
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
package learn.thrift.bean;

public class Friends implements org.apache.thrift.TBase<Friends, Friends._Fields>, java.io.Serializable, Cloneable {
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Friends");

private static final org.apache.thrift.protocol.TField NO_FIELD_DESC = new org.apache.thrift.protocol.TField("No", org.apache.thrift.protocol.TType.I16, (short)1);

private 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());
}

public short No; // required

/** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
public enum _Fields implements org.apache.thrift.TFieldIdEnum {
NO((short)1, "No");

private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();

static {
for (_Fields field : EnumSet.allOf(_Fields.class)) {
byName.put(field.getFieldName(), field);
}
}

/**
* Find the _Fields constant that matches fieldId, or null if its not found.
*/
public static _Fields findByThriftId(int fieldId) {
switch(fieldId) {
case 1: // NO
return NO;
default:
return null;
}
}

/**
* Find the _Fields constant that matches fieldId, throwing an exception
* if it is not found.
*/
public static _Fields findByThriftIdOrThrow(int fieldId) {
_Fields fields = findByThriftId(fieldId);
if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!");
return fields;
}

/**
* Find the _Fields constant that matches name, or null if its not found.
*/
public static _Fields findByName(String name) {
return byName.get(name);
}

private final short _thriftId;
private final String _fieldName;

_Fields(short thriftId, String fieldName) {
_thriftId = thriftId;
_fieldName = fieldName;
}

public short getThriftFieldId() {
return _thriftId;
}

public String getFieldName() {
return _fieldName;
}
}

// isset id assignments
private static final int __NO_ISSET_ID = 0;
private BitSet __isset_bit_vector = new BitSet(1);
public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
static {
Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
tmpMap.put(_Fields.NO, new org.apache.thrift.meta_data.FieldMetaData("No", org.apache.thrift.TFieldRequirementType.REQUIRED,
new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I16)));
metaDataMap = Collections.unmodifiableMap(tmpMap);
org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Friends.class, metaDataMap);
}

public Friends() {
}

public Friends(
short No)
{
this();
this.No = No;
setNoIsSet(true);
}

/**
* Performs a deep copy on <i>other</i>.
*/
public Friends(Friends other) {
__isset_bit_vector.clear();
__isset_bit_vector.or(other.__isset_bit_vector);
this.No = other.No;
}

public Friends deepCopy() {
return new Friends(this);
}

@Override
public void clear() {
setNoIsSet(false);
this.No = 0;
}

public short getNo() {
return this.No;
}

public Friends setNo(short No) {
this.No = No;
setNoIsSet(true);
return this;
}

public void unsetNo() {
__isset_bit_vector.clear(__NO_ISSET_ID);
}

/** Returns true if field No is set (has been assigned a value) and false otherwise */
public boolean isSetNo() {
return __isset_bit_vector.get(__NO_ISSET_ID);
}

public void setNoIsSet(boolean value) {
__isset_bit_vector.set(__NO_ISSET_ID, value);
}

public void setFieldValue(_Fields field, Object value) {
switch (field) {
case NO:
if (value == null) {
unsetNo();
} else {
setNo((Short)value);
}
break;

}
}

public Object getFieldValue(_Fields field) {
switch (field) {
case NO:
return Short.valueOf(getNo());

}
throw new IllegalStateException();
}

/** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
public boolean isSet(_Fields field) {
if (field == null) {
throw new IllegalArgumentException();
}

switch (field) {
case NO:
return isSetNo();
}
throw new IllegalStateException();
}

@Override
public boolean equals(Object that) {
if (that == null)
return false;
if (that instanceof Friends)
return this.equals((Friends)that);
return false;
}

public boolean equals(Friends that) {
if (that == null)
return false;

boolean this_present_No = true;
boolean that_present_No = true;
if (this_present_No || that_present_No) {
if (!(this_present_No && that_present_No))
return false;
if (this.No != that.No)
return false;
}

return true;
}

@Override
public int hashCode() {
return 0;
}

public int compareTo(Friends other) {
if (!getClass().equals(other.getClass())) {
return getClass().getName().compareTo(other.getClass().getName());
}

int lastComparison = 0;
Friends typedOther = (Friends)other;

lastComparison = Boolean.valueOf(isSetNo()).compareTo(typedOther.isSetNo());
if (lastComparison != 0) {
return lastComparison;
}
if (isSetNo()) {
lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.No, typedOther.No);
if (lastComparison != 0) {
return lastComparison;
}
}
return 0;
}

public _Fields fieldForId(int fieldId) {
return _Fields.findByThriftId(fieldId);
}

public 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);
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder("Friends(");
boolean first = true;

sb.append("No:");
sb.append(this.No);
first = false;
sb.append(")");
return sb.toString();
}

public void validate() throws org.apache.thrift.TException {
// check for required fields
// alas, we cannot check 'No' because it's a primitive and you chose the non-beans generator.
}

private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
try {
write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
} catch (org.apache.thrift.TException te) {
throw new java.io.IOException(te);
}
}

private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
try {
// it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
__isset_bit_vector = new BitSet(1);
read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
} catch (org.apache.thrift.TException te) {
throw new java.io.IOException(te);
}
}

private static class FriendsStandardSchemeFactory implements SchemeFactory {
public FriendsStandardScheme getScheme() {
return new FriendsStandardScheme();
}
}

private 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();
}

}

private static class FriendsTupleSchemeFactory implements SchemeFactory {
public FriendsTupleScheme getScheme() {
return new FriendsTupleScheme();
}
}

private static class FriendsTupleScheme extends TupleScheme<Friends> {

@Override
public void write(org.apache.thrift.protocol.TProtocol prot, Friends struct) throws org.apache.thrift.TException {
TTupleProtocol oprot = (TTupleProtocol) prot;
oprot.writeI16(struct.No);
}

@Override
public void read(org.apache.thrift.protocol.TProtocol prot, Friends struct) throws org.apache.thrift.TException {
TTupleProtocol iprot = (TTupleProtocol) prot;
struct.No = iprot.readI16();
struct.setNoIsSet(true);
}
}

}

代码详解

类图

下面我们会对上文的代码做详细分析。首先我们来看一下类图。

我们可以看到 Friends 是继承 TBase , TBase 又实现 TFieldIdEnum
我们来看TBase 代码

基础数据结构

TBase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface TBase<T extends TBase<?, ?>, F extends TFieldIdEnum> extends Comparable<T>, Serializable {
void read(TProtocol var1) throws TException;

void write(TProtocol var1) throws TException;

F fieldForId(int var1);

boolean isSet(F var1);

Object getFieldValue(F var1);

void setFieldValue(F var1, Object var2);

TBase<T, F> deepCopy();

void clear();
}
// TFieldIdEnum
public interface TFieldIdEnum {
short getThriftFieldId();

String getFieldName();
}

TBaseThrift 的所有Type 类型的基类接口,其中定了几个比较重要的方法,比如说读写,克隆和清空。 而TFieldIdEnum 只是一个id 和名字的枚举。 从这里可以看到,实现 TBase的类具有可序列化的功能。

TStruct

接下来是 本 Struct 的一个描述, TStruct 只是对 struct 名字的一个描述的封装类,并没有什么特别。

1
2
3
4
5
6
7
8
9
10
11
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Friends");
// TStruct.java
public final class TStruct {
public final String name;
public TStruct() {
this("");
}
public TStruct(String n) {
this.name = n;
}
}

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

TMessage

这个是 封装结构元数据的上层类, 当我们调用RPC 的时候, Thrift 会将我们要求我们按照他的格式封装元数据,然后在通过TMessage 封装元数据,然后通过网络发送出去。

1
2
3
4
5
6
public 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
23
public 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
public 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final class TSet {
public final byte elemType;
public final int size;

public TSet() {
this((byte)0, 0);
}

public TSet(byte t, int s) {
this.elemType = t;
this.size = s;
}

public TSet(TList list) {
this(list.elemType, list.size);
}
}

TList

1
2
3
4
5
6
7
8
9
10
11
12
13
public final class TList {
public TList() {
this(TType.STOP, 0);
}

public TList(byte t, int s) {
elemType = t;
size = s;
}

public final byte elemType;
public final int size;
}

TField

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TField {
public final String name;
public final byte type;
public final short id;
public TField() {
this("", TType.STOP, (short)0);
}

public TField(String n, byte t, short i) {
name = n;
type = t;
id = i;
}

public String toString() {
return "<TField name:'" + name + "' type:" + type + " field-id:" + id + ">";
}

public boolean equals(TField otherField) {
return type == otherField.type && id == otherField.id;
}
}

Friends 中每个变量都会用 TField 来封装,如:

1
2
private static final TField NO_FIELD_DESC = 
new TField("No", TType.I16, (short)1);

里面记录了 Field 的名字,类型,和FieldId , thrift 会给每一个Struct的成员变量增加一个Field封装类。

IScheme

IScheme 的作用

除了变量的定义之外 Friends 类当中还有一个概念 叫 Scheme

1
2
3
4
5
private 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 这个接口。 从而实现了 readwrite 方法。

这个 Scheme 是干什么呢, 和 FriendsStandardScheme, Friends 又是什么关系呢?

我们知道,Friends 是继承 TBaseTBase 中有两个重要的方法

1
2
void read(TProtocol var1) throws TException;
void write(TProtocol var1) throws TException;

Friends 就是要实现这两个方法,

1
2
3
4
5
6
7
public 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
11
private static class FriendsTupleSchemeFactory implements SchemeFactory {
public FriendsTupleScheme getScheme() {
return new FriendsTupleScheme();
}
}

private static class FriendsStandardSchemeFactory implements SchemeFactory {
public FriendsStandardScheme getScheme() {
return new FriendsStandardScheme();
}
}

IScheme 具体实现方式。

下面我们具体来分析一下 FriendsStandardSchemeFriendsTupleSchemeFactory 具体的读写策略。

FriendsStandardScheme

我们来具体分析一下 FriendsStandardScheme

1
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
45
private 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
9
public 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static class FriendsTupleScheme extends TupleScheme<Friends> {

@Override
public void write(org.apache.thrift.protocol.TProtocol prot, Friends struct) throws org.apache.thrift.TException {
TTupleProtocol oprot = (TTupleProtocol) prot;
oprot.writeI16(struct.No);
}

@Override
public void read(org.apache.thrift.protocol.TProtocol prot, Friends struct) throws org.apache.thrift.TException {
TTupleProtocol iprot = (TTupleProtocol) prot;
struct.No = iprot.readI16();
struct.setNoIsSet(true);
}
}

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
2
3
4
5
6
7
8
private static final int __NO_ISSET_ID = 0;
private BitSet __isset_bit_vector = new BitSet(1);

public Friends(short No){
this();
this.No = No;
setNoIsSet(true);
}

同样我们还可以判断这个指是否

1
2
3
public boolean isSetNo() {
return __isset_bit_vector.get(__NO_ISSET_ID);
}

FieldMetaData 结构

Thrift 对于每个生成的类会有一个 FieldMetaData 的结构
这个类会和 Field 一个名称的枚举类型来对应成一个map结构。
FieldMetaData 主要是封装了 Field 值的一些元数据。

1
2
3
4
5
6
7
8
public static final Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
static {
Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
tmpMap.put(_Fields.NO, new org.apache.thrift.meta_data.FieldMetaData("No", org.apache.thrift.TFieldRequirementType.REQUIRED,
new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I16)));
metaDataMap = Collections.unmodifiableMap(tmpMap);
org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Friends.class, 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
17
public 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 字段的元数据
}
// ....
}

Friendsnew 一个这个出来,并传入了相应的信息,我们struct的中的required optional 的关键字 就是通过 这个常量类来定义的。

1
2
3
4
5
6
7
8
public 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
7
public 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
2
Friends friends = new Friends();
friends.getFieldValue(Friends._Fields.NO);

复杂符合类型

我们的 Friends 这是简单定义了 Friends 一个i16的字段No
如果我们在Friends 里增加一些复杂类型,Java 文件又是什么情况呢?

这里还要还有一个问题,我们只是分析了Struct 的生成类,那么Service的生成类是怎样的呢?

由于篇幅问题,我们这个话题留在下期去讨论。

总结

本节主要是总结了通过struct 文件生成的代码,主要谈论了框架生成的一些特性。更有一些协议层的相关的方法,thrift 的Java 代码非常轻量级,分层也非常明确,那么这些东西将会如何组织,整个的分层又是什么呢?我们下一节在详细论述。



支付宝打赏 微信打赏

赞赏一下