学习thrift源码主要为了弄清楚几个问题
这里我们只分析生成的java代码,使用thrift使用和源码分析中的demo
生成java代码可以有多种方式,假设IDL文件名是mythrift.thrift
那么这两种有什么区别呢? 可以在命令行中直接输入thrift回车,就可以看到相关解释
可以看到除了beans的方式外,还有其他多种生成java的方式:
这点我们可以从生成的源码去看下,是显而易见的
DemoClient.java
public class DemoClient {
public static void main(String[] args) throws Exception{
// 创建Transport
TTransport transport = new TSocket("localhost", 9090, 5000);
//创建protocol
TProtocol protocol = new TBinaryProtocol(transport);
//创建客户端
MyService.Client client = new MyService.Client(protocol);
transport.open();
Stu stu = new Stu();
stu.setAge(23);
Teacher teacher = new Teacher("jack", "32");
client.printStu(stu, teacher);
transport.close();
}
}
TSocket封装了Socket连接, 其继承关系如下
TSocket中包括java的socket,TIOStreamTransport中包括InputStream和OutputStream,其InputStream和OutputStream是由TSocket中的socket创建的。总结来说,TSocket封装了底层网络IO的方法
TProtocol是一个抽象类,而TBinaryProtocol是TProtocol的一种实现之一,用于二进制形式的序列化。TProtocol的实现类包括如下
TProtocol中包含一个TSocket,使用TSocket来将数据经过编码后写入和读出到网络中。
TSocket和TProtocol都是我们引入的thrift依赖中引入的类
和TProtocol与TSocket不同,MyService是我们在IDL中定义的Server,然后使用thrift命令生成的java代码
MyService中包含了很多的内部类
客户端使用的是MyService.Client。Client中有我们在IDL中定义的方法,当需要调用远程方法的时候,就可以直接调用,例如client.printStu。以客户端调用printStu为例介绍下客户端是如何序列化参数然后写到网络中的:
当客户端调用printStu的时候会调用send_printStu方法
public void send_printStu(Stu stu, Teacher teacher) throws org.apache.thrift.TException
{
printStu_args args = new printStu_args(); // 代表printStu的参数,重点关注
args.setStu(stu);
args.setTeacher(teacher);
sendBase("printStu", args); // 发送到网络
}
protected void sendBase(String methodName, TBase args) throws TException {
oprot_.writeMessageBegin(new TMessage(methodName, TMessageType.CALL, ++seqid_)); // seqid_从0开始
args.write(oprot_); // printStu_args的write方法来写方法的参数
oprot_.writeMessageEnd(); // TProtocol的方法
oprot_.getTransport().flush();
}
public void writeMessageBegin(TMessage message) throws TException {
if (strictWrite_) { // 将方法的名字、类型和id通过网络写到socket中,既然后id序列号了,为甚还要写名字?
int version = VERSION_1 | message.type;
writeI32(version);
writeString(message.name); //可见方法名字短点也能提高RPC性能
writeI32(message.seqid);
} else {
writeString(message.name);
writeByte(message.type);
writeI32(message.seqid);
}
}
使用writeMessageBegins将方法名字写到网络后,会使用MyService.printStu_args##write,将方法的参数写到网络中
printStu是我们在定义在IDL中的方法,生成的代码中会用printStu_args来表示该方法的参数,
printStu_args中定义了write和read方法用于将参数写到socket中
public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
}
其write方法又调用了printStu_argsStandardScheme#write,printStu_argsStandardScheme是定义在printStu_args中的内部类,用来真正的读写参数
前面说了 printStu_argsStandardScheme是MyService.printStu_args的内部类,它只有两个方法:read和write,用于真正的读写printStu的参数,我们这里看下参数是怎么被写到网络中的
贴下部分IDL,方便比较
service MyService {
void printStu(1:Stu stu, 2:Teacher teacher) // 主要看这个
void printStu2(1:Stu stu, 2:Teacher teacher)
}
public void write(org.apache.thrift.protocol.TProtocol oprot, printStu_args struct) throws org.apache.thrift.TException {
struct.validate(); // printStu_args#validate进行校验,会校验参数是带require的,如果为null则抛出异常
oprot.writeStructBegin(STRUCT_DESC);
if (struct.stu != null) { // 不为null才发送
oprot.writeFieldBegin(STU_FIELD_DESC); // 写参数开头
struct.stu.write(oprot); // 参数的具体值
oprot.writeFieldEnd();
}
if (struct.teacher != null) {
oprot.writeFieldBegin(TEACHER_FIELD_DESC);
struct.teacher.write(oprot);
oprot.writeFieldEnd();
}
oprot.writeFieldStop();
oprot.writeStructEnd();
}
// oprot.writeFieldBegin 也就是TBinaryProtocol#writeFieldBegin
public void writeFieldBegin(TField field) throws TException {
writeByte(field.type); //只写了类型和id,没有写参数的名字
writeI16(field.id);
}
从writeFieldBegin我们可以看到,在序列化写方法参数的时候并没有用到参数的名字,而是只用了id,这也就是为什么IDL中参数要写上序号的原因。至此我们解决了开头的第2个问题
写完参数的类型和id后,会继续写参数的值,以Stu为例
struct Stu {
1:i32 name = 13
2:required i32 age
3:optional i32 height = 23
}
和printStu_args类似,Stu真正写的时候同样是调用了Stu的内部类StuStandardScheme进行写。StuStandardScheme也只有read和write方法
public void write(org.apache.thrift.protocol.TProtocol oprot, Stu struct) throws org.apache.thrift.TException {
struct.validate(); // 重点关注
oprot.writeStructBegin(STRUCT_DESC);
oprot.writeFieldBegin(NAME_FIELD_DESC); // 同样只写了id和类型
oprot.writeI32(struct.name);
oprot.writeFieldEnd();
oprot.writeFieldBegin(AGE_FIELD_DESC);
oprot.writeI32(struct.age);
oprot.writeFieldEnd();
if (struct.isSetHeight()) { // height是 optional,只有set了才能序列化
oprot.writeFieldBegin(HEIGHT_FIELD_DESC);
oprot.writeI32(struct.height);
oprot.writeFieldEnd();
}
oprot.writeFieldStop();
oprot.writeStructEnd();
}
// Stu#validate
public void validate() throws org.apache.thrift.TException {
// check for required fields
if (!isSetAge()) { // age是require,会进行校验,没有set就抛异常
throw new org.apache.thrift.protocol.TProtocolException("Required field 'age' is unset! Struct:" + toString());
}
}
//
public boolean isSetAge() { // 用一个BitSet来标记属性是否被set
return __isset_bit_vector.get(__AGE_ISSET_ID);
}
public void setAge(int age) {
this.age = age;
setAgeIsSet(true); // 只有在调用setter的方法的的时候才会将set标记
}
看下有默认值的是怎么回事,也就是开头的第4个问题
public Stu() {
this.name = 13; // default
this.height = 23; // optional, 并没有调用setter,因此即使写了默认值也不会被序列化
}
// 构造函数只有default和require类型
public Stu(
int name, // default
int age) // require
{
this();
this.name = name;
setNameIsSet(true);
this.age = age;
setAgeIsSet(true);
}
由以上我们可以总结下:
引用类型的写和原生类型存在差异
struct Teacher {
1:string name = "13"
2:required string age = "14"
3:optional string height = "15"
}
public void write(org.apache.thrift.protocol.TProtocol oprot, Teacher struct) throws org.apache.thrift.TException {
struct.validate(); // 检查参数
oprot.writeStructBegin(STRUCT_DESC);
if (struct.name != null) { // 只要不是null就写入,因为有校验,所以require肯定不会为null
oprot.writeFieldBegin(NAME_FIELD_DESC);
oprot.writeString(struct.name);
oprot.writeFieldEnd();
}
if (struct.age != null) { // 和原生类型不同,default不一定会被序列化
oprot.writeFieldBegin(AGE_FIELD_DESC);
oprot.writeString(struct.age);
oprot.writeFieldEnd();
}
if (struct.height != null) {
if (struct.isSetHeight()) {
oprot.writeFieldBegin(HEIGHT_FIELD_DESC);
oprot.writeString(struct.height);
oprot.writeFieldEnd();
}
}
oprot.writeFieldStop();
oprot.writeStructEnd();
}
}
public void validate() throws org.apache.thrift.TException {
// check for required fields
if (!isSetAge()) { // 只校验require参数,这点和原生类型相同
throw new org.apache.thrift.protocol.TProtocolException("Required field 'age' is unset! Struct:" + toString());
}
}
// 和原生类型不同,判断是否set只是根据参数是否为null
public boolean isSetAge() {
return this.age != null;
}
由以上我们可以总结下:
服务端和客户端其实类似,主要注意以下一点
MyService.Processor接收一个继承自Iface的参数,也就是我们的实现类MyServiceImpl。在MyService.Processor中有个方法,会将每个方法对应的处理方法存在在一个map中:
private static <I extends Iface> Map<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> getProcessMap(Map<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> processMap) {
processMap.put("printStu", new printStu());
processMap.put("printStu2", new printStu2());
return processMap;
}
手机扫一扫
移动阅读更方便
你可能感兴趣的文章