前言
在了解了Transport、TProtocol层的接口后,这一章我们来研究Thrift在客户端调用了方法后,是如何将我们传递的参数对象进行解析并发送出去的,服务端是如何将字节数组还原成一个对象的。
Thrift示例
由于所有的序列化、反序列化操作、客户端的生成等都是在Thrift编译器生成的代码中,所以我们创建一个Thrift文件并生成Java类,后续都以该文件的源码来讲解,为了方便理解,这里的示例很简单,定义了一个Hello接口,一个example对象。
namespace java thriftgoal
service Hello{
string hello(1:example para1,2:string para2)
}
struct example{
1:string exampl1,
2:i32 exampl2;
}
上面代码会生成Hello.java,example.java两个文件,其中example.java只是一个Java中的bean,在我们搞清Hello.java内部结构后,example.java自然就可以理解,下面是Hello.java内部类图结构:
其实所有的service接口生成的文件都包含这几个类,不过实现是根据定义文件有所不同,每个类的功能如下:
- Iface:我们在service Hello中定义的方法,也即对外提供的服务接口。
- AsyncIface:Iface的异步接口。
- Client:客户端调用时使用,实现了Iface接口。
- AsyncClient:异步客户端使用,实现了AsyncIface接口,这里不讲。
- Processor:业务执行器,服务端使用。
- AsyncProcessor:异步业务执行器,服务端使用,,这里不讲。
- hello_args:hello方法的参数形成的一个类,这里如果有多个方法hello,则还会有hello2_args、hello3_args等等
- hello_result:hello方法的返回结果形成的一个类,同7类似。
Client
我们从客户端调用Client的顺序向下探索,首先我们调用了hello方法,源码如下。
//静态类,继承了TServiceClient(所有生成的Client代码都会继承该类),实现了Iface接口,也即为什么我们创建了Client即可调用相应的方法
public static class Client extends org.apache.thrift.TServiceClient implements Iface {
//工厂类,生成Client,没有逻辑,省略
public static class Factory implements org.apache.thrift.TServiceClientFactory<Client> {
}
public Client(org.apache.thrift.protocol.TProtocol prot) {
super(prot, prot);
}
public Client(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
super(iprot, oprot);
}
//客户端调用的方法入口
public java.lang.String hello(example para1, java.lang.String para2) throws org.apache.thrift.TException {
this.send_hello(para1, para2);//先发送出请求
return this.recv_hello();//接收服务端的结果
}
public void send_hello(example para1, java.lang.String para2) throws org.apache.thrift.TException {
//将参数构造成hello_args对象,调用父类的方法发出请求
hello_args args = new hello_args();
args.setPara1(para1);
args.setPara2(para2);
this.sendBase("hello", args);//hello是调用服务端的方法名
}
public java.lang.String recv_hello() throws org.apache.thrift.TException {
//构造结果对象,调用父类方法接收服务端返回结果
hello_result result = new hello_result();
this.receiveBase(result, "hello");//hello是调用服务端的方法名
if (result.isSetSuccess()) {
return result.success;//所有返回结果的变量名称都是success
}
throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT,
"hello failed: unknown result");
}
}
总结下,hello方法分两步,第一步发出请求,第二步接收结果(所有生成的Client都是分为这两步),而发送和接收都需要依赖父类的方法,所以我们下面看看它的父类TServiceClient是如何实现sendBase、receiveBase。
public abstract class TServiceClient {
protected TProtocol iprot_;//输入
protected TProtocol oprot_;//输出
protected int seqid_;//客户端服务端 版本号
public TServiceClient(TProtocol prot) {
this(prot, prot);
}
public TServiceClient(TProtocol iprot, TProtocol oprot) {
iprot_ = iprot;
oprot_ = oprot;
}
public TProtocol getInputProtocol() {
return this.iprot_;
}
public TProtocol getOutputProtocol() {
return this.oprot_;
}
//在上面Client中调用该方法,TMessageType.CALL表示发出请求
protected void sendBase(String methodName, TBase<?,?> args) throws TException {
sendBase(methodName, args, TMessageType.CALL);
}
//发请求时,先发一个message表示此次请求开始,相对应也有一个message结尾,中间的数据部分由args调用write实现,这里我们传入的是 hello_args
private void sendBase(String methodName, TBase<?,?> args, byte type) throws TException {
oprot_.writeMessageBegin(new TMessage(methodName, type, ++seqid_));
args.write(oprot_);
oprot_.writeMessageEnd();
oprot_.getTransport().flush();//将数据发送出去
}
//接收返回结果,也是先读message信息,然后调用result.read实现,result这里具体是hello_result
protected void receiveBase(TBase<?,?> result, String methodName) throws TException {
TMessage msg = iprot_.readMessageBegin();//读消息开始
//异常情况的处理
if (msg.type == TMessageType.EXCEPTION) {
TApplicationException x = new TApplicationException();
x.read(iprot_);
iprot_.readMessageEnd();
throw x;
}
System.out.format("Received %d%n", msg.seqid);
//客户端与服务端版本号不同时抛出异常
if (msg.seqid != seqid_) {
throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID, String.format("%s failed: out of sequence response: expected %d but got %d", methodName, seqid_, msg.seqid));
}
result.read(iprot_);//解析返回结果
iprot_.readMessageEnd();
}
//该方法没见到有用,意思是只是一次单向请求,不需要服务端返回结果
protected void sendBaseOneway(String methodName, TBase<?,?> args) throws TException {
sendBase(methodName, args, TMessageType.ONEWAY);
}
}
从sendBase、receiveBase可以看到,TServiceClient是一个模版,规定了客户端对数据的解析都是以TMessage开头结尾的,真正的数据都是由生成的hello_args、hello_result来实现,所以下一步,我们来看看hello_args是如何通过TProtocol实现读写操作的。
hello_args
hello_args源码大概有400行,这里不能一一列举,所以从上面调用到的hello_args.write(TProtocol t)来入手,同时也看看服务端调用的hello_args.read(TProtocol t)方法。
//这两个方法都是调用了hello_args私有的静态方法scheme来获取一个IScheme对象,然后调用IScheme的read write方法
public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
scheme(iprot).read(iprot, this);
}
public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
scheme(oprot).write(oprot, this);
}
这里我们看看scheme方法的实现。
private static <S extends org.apache.thrift.scheme.IScheme> S scheme(org.apache.thrift.protocol.TProtocol proto) {
return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY: TUPLE_SCHEME_FACTORY).getScheme();
}
在hello_args生成的代码中有两种不同的IScheme(IScheme接口我也暂不清楚它的作用,可理解为基于TProtocol的不同发送实现),standard、tuple类型,这里是根据TProtocol设置的scheme类型获取不同的对象,一般来说我们都是使用standard,所以这里是调用了STANDARD_SCHEME_FACTORY.getScheme()。
private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new hello_argsStandardSchemeFactory();
private static class hello_argsStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory {
public hello_argsStandardScheme getScheme() {
return new hello_argsStandardScheme();
}
}
STANDARD_SCHEME_FACTORY是hello_args的一个静态变量,从上面的代码我们已经了解到从该变量获取到IScheme对象后,会调用相应的read、write,所以我们需要知道hello_argsStandardScheme究竟是如何实现read、write。
//该类继承自StandardScheme,而StandardScheme是一个抽象类,并且实现了IScheme接口,没有任何实现,这里不列举,所以需要由hello_argsStandardScheme来实现write,read方法,而write,read的参数都是需要一个TProtocol,一个TStruct(hello_args继承自TStruct),方法作用还是很好理解,通过TProtocol将TStruct发送,读入
private static class hello_argsStandardScheme extends org.apache.thrift.scheme.StandardScheme<hello_args> {
//读参数的方法是服务端在调用
public void read(org.apache.thrift.protocol.TProtocol iprot, hello_args struct) throws org.apache.thrift.TException {
org.apache.thrift.protocol.TField schemeField;
iprot.readStructBegin();//对应write中方法中的writeStructBegin()
//开始读hello_args对象后,会一直循环,直到读到的schemeField字段类型为STOP,表示该对象的所有字段已经读完,否则根据schemeField的id来判断是哪个字段,然后进行不同的解析
while (true) {
schemeField = iprot.readFieldBegin();//读取当前字段开始
if (schemeField.type == org.apache.thrift.protocol.TType.STOP) {
break;
}
switch (schemeField.id) {
case 1: // para1参数id=1,与文件中的序号是对应的
if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) {
struct.para1 = new example();//这块不多讲,example生成的代码和hello_args结构是一样的,调用其相应的read方法即可,和这里的逻辑类似
struct.para1.read(iprot);
struct.setPara1IsSet(true);
} else {
org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
}
break;
case 2: // param2参数id=2,与文件中的序号对应
if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
struct.para2 = iprot.readString();//设置para2的值,hello_args会为hello方法参数列表的每个参数生成的一个本地变量用于保存
struct.setPara2IsSet(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();//读当前对象结束
struct.validate();//对象校验,这一步略过
}
public void write(org.apache.thrift.protocol.TProtocol oprot, hello_args struct)
throws org.apache.thrift.TException {
struct.validate();//参数校验
//写的话比较简单,就是按顺序,写出去
oprot.writeStructBegin(STRUCT_DESC);//与上面read方法中的readStructBegin对应
if (struct.para1 != null) {
oprot.writeFieldBegin(PARA1_FIELD_DESC);
struct.para1.write(oprot);//调用example的write,example与hello_args结构类似,内部逻辑相同
oprot.writeFieldEnd();
}
if (struct.para2 != null) {
oprot.writeFieldBegin(PARA2_FIELD_DESC);
oprot.writeString(struct.para2);
oprot.writeFieldEnd();
}
//上面STRUCT_DESC,PARA1_FIELD_DESC,PARA2_FIELD_DESC都是hello_args的静态变量,具体在下面展示
oprot.writeFieldStop();//hello_args中的所有的字段都已经写完
oprot.writeStructEnd();//hello_args对象结尾
}
}
//Thrift会将hello方法中的参数编译为hello_args类,并且里面会有hello_args类的一个TStruct对象,hello方法中的每个参数都会编译为本地的一个TField型静态变量,里面标识了字段的名称、类型、序号,忘记的同学回顾下上一章TField的相关概念
private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct(
"hello_args");
private static final org.apache.thrift.protocol.TField PARA1_FIELD_DESC = new org.apache.thrift.protocol.TField(
"para1", org.apache.thrift.protocol.TType.STRUCT, (short) 1);
private static final org.apache.thrift.protocol.TField PARA2_FIELD_DESC = new org.apache.thrift.protocol.TField(
"para2", org.apache.thrift.protocol.TType.STRING, (short) 2);
这一部分源码向大家展示了客户端真实调用TProtocol的地方是如何进行读写操作,hello_result、example的结构都是一样的,这里就不重复说明了,下面给两幅图帮助理解:
客户端发送数据的write调用,服务端接收时,也是按相同的顺序read。
这幅图不是很严谨,只要大家根据序号能清楚方法间的调用关系即可,另外,服务端的顺序也是对应的,不过是先接收参数,再发送返回结果,在讲TProcessor时不再多说。
前面几个章节主要是向大家介绍了客户端如何将对象参数转为自定义的规则发送出去,服务端的原理也相同,不过服务端会有业务逻辑处理,下一章我们来看看Hello.java中TProcessor类的实现。
TProcessor
//该类继承自TBaseProcessor,关于TProcessor接口已经在第一篇博客中讲过了
public static class Processor<I extends Iface> extends org.apache.thrift.TBaseProcessor<I> implements
org.apache.thrift.TProcessor {
private static final org.slf4j.Logger _LOGGER = org.slf4j.LoggerFactory.getLogger(Processor.class.getName());
//创建服务端时,需要传入一个业务实现对象
public Processor(I iface) {
super(
iface,
getProcessMap(new java.util.HashMap<java.lang.String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>>()));
}
protected Processor(
I iface,
java.util.Map<java.lang.String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> processMap) {
super(iface, getProcessMap(processMap));
}
//上面两个构造函数都会调用该方法,作用就是将往map放入一个hello对象
private static <I extends Iface> java.util.Map<java.lang.String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> getProcessMap(java.util.Map<java.lang.String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> processMap) {
processMap.put("hello", new hello());//这里如果我们定义了多个方法就还会put hello2、hello3等等,hello是一个执行函数,后面讲
return processMap;
}
}
//TProcessor的父类
public abstract class TBaseProcessor<I> implements TProcessor {
private final I iface;
private final Map<String, ProcessFunction<I, ? extends TBase>> processMap;//保存函数名称与函数方法的map
protected TBaseProcessor(I iface, Map<String, ProcessFunction<I, ? extends TBase>> processFunctionMap) {
this.iface = iface;
this.processMap = processFunctionMap;
}
public Map<String, ProcessFunction<I, ? extends TBase>> getProcessMapView() {
return Collections.unmodifiableMap(this.processMap);
}
//实现process的地方
public boolean process(TProtocol in, TProtocol out) throws TException {
TMessage msg = in.readMessageBegin();//读message开始,与上节TServiceClient的发送参数时的writeMessageBegin相对应
ProcessFunction fn = this.processMap.get(msg.name);//获取到执行函数
//服务端没有该方法时的处理逻辑
if (fn == null) {
TProtocolUtil.skip(in, TType.STRUCT);
in.readMessageEnd();
TApplicationException x = new TApplicationException(TApplicationException.UNKNOWN_METHOD,
"Invalid method name: '" + msg.name + "'");
out.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid));
x.write(out);
out.writeMessageEnd();
out.getTransport().flush();
return true;
}
fn.process(msg.seqid, in, out, this.iface);//调用执行函数
return true;
}
}
关于TProcessor,重点在TBaseProcessor中,已经定义好了process的执行逻辑,子类TProcessor只需要将相应的函数名称、对象放进map里即可,process会根据客户端的方法名称取出执行函数来执行业务,下面看看hello这个TProcessor中的静态内部类是如何实现业务执行的。
//继承自ProcessFunction,父类已经定义好了方法模版,如果还有hello2 hello3方法,这里也会有相应的静态内部类hello2.java,hello3.java
public static class hello<I extends Iface> extends org.apache.thrift.ProcessFunction<I, hello_args> {
public hello() {
super("hello");//函数的名称
}
@Override
public hello_args getEmptyArgsInstance() {
return new hello_args(); //构造空参数对象,来接客户端的请求
}
@Override
protected boolean isOneway() {
return false;
}
@Override //执行业务逻辑
public hello_result getResult(I iface, hello_args args) throws org.apache.thrift.TException {
hello_result result = new hello_result();
result.success = iface.hello(args.para1, args.para2);//执行业务逻辑,并将返回值赋给success变量,success是一个咱们在Thrift中定义的方法返回值的类型变量,这里是一个string类型变量
return result;
}
}
//所有执行函数的父类
public abstract class ProcessFunction<I, T extends TBase> {
private final String methodName;//函数名称
private static final Logger LOGGER = LoggerFactory.getLogger(ProcessFunction.class.getName());
public ProcessFunction(String methodName) {
this.methodName = methodName;
}
public final void process(int seqid, TProtocol iprot, TProtocol oprot, I iface) throws TException {
T args = getEmptyArgsInstance();//这里我们拿到的是hello_args,子类实现
try {
args.read(iprot);//从iprot中读出args参数,详情见hello_args
} catch (TProtocolException e) {
iprot.readMessageEnd();
TApplicationException x = new TApplicationException(TApplicationException.PROTOCOL_ERROR, e.getMessage());
oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.EXCEPTION, seqid));
x.write(oprot);
oprot.writeMessageEnd();
oprot.getTransport().flush();
return;
}
iprot.readMessageEnd();//message结尾,与上节TServiceClient的发送参数时的writeMessageEnd相对应
TBase result = null;
try {
result = getResult(iface, args);//这里也由子类实现,
} catch(TException tex) {
//异常逻辑处理
LOGGER.error("Internal error processing " + getMethodName(), tex);
if (!isOneway()) {
TApplicationException x = new TApplicationException(TApplicationException.INTERNAL_ERROR,
"Internal error processing " + getMethodName());
oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.EXCEPTION, seqid));
x.write(oprot);
oprot.writeMessageEnd();
oprot.getTransport().flush();
}
return;
}
//isOneway()表示是否只是一个单向请求,不需要回应,不是单向请求时需要将结果返回给客户端
if(!isOneway()) {
oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.REPLY, seqid));
result.write(oprot);
oprot.writeMessageEnd();
oprot.getTransport().flush();
}
}
protected abstract boolean isOneway();
public abstract TBase getResult(I iface, T args) throws TException;
public abstract T getEmptyArgsInstance();
public String getMethodName() {
return methodName;
}
}
总结,TBaseProcessor保存了一个 name为key,processFunction为值的map,而具体里面的函数值由子类进行添加即可,在process中根据客户端请求的函数名称获取相应processFunction,再调用processFunction.process,process中会从inTProtocol中解析出一个hello_args参数对象,同时调用子类getResult获取结果,子类getResult将执行Iface的实现,然后将返回结果赋值给hello_result,processFunction拿到result后将数据通过outTProtocol输出。
总结
这一章,大家需要掌握三个知识点即可,一是客户端发送请求,接收响应的整个流程,二是TProcess执行业务逻辑的流程,三是所有生成的Struct结构,包括hello_args,hello_result是如何从TProtocol中读写对象的,理解TMessage、TStruct、TField在读写中的作用,理解了这三点后,下一章我们来看服务器的源码。