IDL是很多RPC框架用来支持跨语言环境调用的一个服务描述组件,一般都是采用文本格式来定义。 更多IDL的思考查看《理解WSDL, IDL》
Thrift的不同版本定义IDL的语法也不太相同,这里使用Thrift-0.8.0这个版本来介绍Java下的IDL定义
1. namespace 定义包名
2. struct 定义服务接口的参数,返回值使用到的类结构。如果接口的参数都是基本类型,则不需要定义struct
3. service 定义接口
一个简单的例子,IDL文件以.thrift为后缀名。
demo.thrift
1. 定义了生成的Java文件的包名为com.thrift.test
2. 定义了一个struct类结构作为参数
3.定义了一个service接口,返回值是int,方法名叫demoMethod,参数有三个,第一个是字符串类型,第二个是上面定义的类Parameter,第三个是Map类型
namespace java com.thrift.test struct Parameter{ 1: required i32 id; 2: required string name; } service DemoService{ i32 demoMethod(1:string param1, 2:Parameter param2, 3:map<string,string> param3); }
IDL支持的数据类型包括以下部分
bool 布尔型 byte 8位整数 i16 16位整数 i32 32位整数 i64 64位整数 double 双精度浮点数 string 字符串 binary 字节数组 list<i16> List集合,必须指明泛型 map<string, string> Map类型,必须指明泛型 set<i32> Set集合,必须指明泛型
有了IDL之后,就可以使用thrift来自动生成辅助代码,包括客户端代码和序列化接口的代码
thrift -r --gen java demo.thrift
生成的代码如下
每个Struct会单独生成一个类,每个Service会生成一个类。
看一下生成类的具体结构
生成的类主要有5个部分
1. 接口类型,默认名称都是Iface。这个接口类型被服务器和客户端共同使用。服务器端使用它来做顶层接口,编写实现类。客户端代码使用它作为生成代理的服务接口。
自动生成的接口有两个,一个是同步调用的Iface,一个是异步调用的AsyncIface。异步调用的接口多了一个回调参数。
public interface Iface { public int demoMethod(String param1, Parameter param2, Map<String,String> param3) throws org.apache.thrift.TException; } public interface AsyncIface { public void demoMethod(String param1, Parameter param2, Map<String,String> param3, org.apache.thrift.async.AsyncMethodCallback<AsyncClient.demoMethod_call> resultHandler) throws org.apache.thrift.TException; }
2. 客户端类型,一个同步调用的客户端Client,一个异步调用的客户端AsyncClient
3. Processor,用来支持方法调用,每个服务的实现类都要使用Processor来注册,这样最后服务器端调用接口实现时能定位到具体的实现类。后面会有专门的文章介绍
4.方法参数的封装类,以"方法名_args"命名
5.方法返回值的封装类,以"方法名_result"命名
看一下生成的同步调用客户端Client的具体代码
1. 提供一个工厂方法来创建Client对象
2.接口方法的客户端代理,只做了两件事,发送方法调用请求;接收返回值
发送方法调用请求做了2件事
1. 创建方法参数对象,封装方法参数
2. 调用父类的sendBase方法来发送消息。发送消息时先通过writeMessageBegin发送消息头,再调用方法参数对象的write(TProtocol)方法发送消息体,最后结束发送
接受调用返回值做了2件事
1. 创建方法返回值对象,封装方法返回值
2. 调用父类的receiveBase方法接收方法返回值。先通过receiveMessage接收消息体,处理异常,然后调用方法参数对象的read(TProtocol)方法来接收消息体,最后结束接收
public static class Client extends org.apache.thrift.TServiceClient implements Iface { public static class Factory implements org.apache.thrift.TServiceClientFactory<Client> { public Factory() {} public Client getClient(org.apache.thrift.protocol.TProtocol prot) { return new Client(prot); } public Client getClient(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) { return new Client(iprot, oprot); } } public int demoMethod(String param1, Parameter param2, Map<String,String> param3) throws org.apache.thrift.TException { send_demoMethod(param1, param2, param3); return recv_demoMethod(); } public void send_demoMethod(String param1, Parameter param2, Map<String,String> param3) throws org.apache.thrift.TException { demoMethod_args args = new demoMethod_args(); args.setParam1(param1); args.setParam2(param2); args.setParam3(param3); sendBase("demoMethod", args); } public int recv_demoMethod() throws org.apache.thrift.TException { demoMethod_result result = new demoMethod_result(); receiveBase(result, "demoMethod"); if (result.isSetSuccess()) { return result.success; } throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "demoMethod failed: unknown result"); } } //org.apache.thrift.TServiceClient.sendBase,客户端的父类方法 protected void sendBase(String methodName, TBase args) throws TException { // 发送消息头 oprot_.writeMessageBegin(new TMessage(methodName, TMessageType.CALL, ++seqid_)); // 发送消息体,由方法参数对象自己处理编解码 args.write(oprot_); oprot_.writeMessageEnd(); oprot_.getTransport().flush(); } protected void receiveBase(TBase result, String methodName) throws TException { // 接收消息头 TMessage msg = iprot_.readMessageBegin(); if (msg.type == TMessageType.EXCEPTION) { TApplicationException x = TApplicationException.read(iprot_); iprot_.readMessageEnd(); throw x; } if (msg.seqid != seqid_) { throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID, methodName + " failed: out of sequence response"); } //由返回值对象自己处理编解码 result.read(iprot_); iprot_.readMessageEnd(); }
看一下方法参数对象
方法参数实现了TBase接口,TBase接口定义了一个对象在某种协议下的编解码接口。
public interface TBase<T extends TBase<?,?>, F extends TFieldIdEnum> extends Comparable<T>, Serializable { public void read(TProtocol iprot) throws TException; public void write(TProtocol oprot) throws TException; }
方法参数对象主要做了2件事
1. 创建每个参数的元数据,包括参数类型,顺序号。顺序号是在IDL定义的时候设置的,用来识别参数的位置,在编解码的时候有用
2. 实现自己的编解码方法, read(TProtocol), write(TProtocol)。这里又把具体的编解码功能委托给了XXXScheme类
public static class demoMethod_args implements org.apache.thrift.TBase<demoMethod_args, demoMethod_args._Fields>, java.io.Serializable, Cloneable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("demoMethod_args"); private static final org.apache.thrift.protocol.TField PARAM1_FIELD_DESC = new org.apache.thrift.protocol.TField("param1", org.apache.thrift.protocol.TType.STRING, (short)1); private static final org.apache.thrift.protocol.TField PARAM2_FIELD_DESC = new org.apache.thrift.protocol.TField("param2", org.apache.thrift.protocol.TType.STRUCT, (short)2); private static final org.apache.thrift.protocol.TField PARAM3_FIELD_DESC = new org.apache.thrift.protocol.TField("param3", org.apache.thrift.protocol.TType.MAP, (short)3); private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>(); static { schemes.put(StandardScheme.class, new demoMethod_argsStandardSchemeFactory()); schemes.put(TupleScheme.class, new demoMethod_argsTupleSchemeFactory()); } public String param1; // required public Parameter param2; // required public Map<String,String> param3; // 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 { PARAM1((short)1, "param1"), PARAM2((short)2, "param2"), PARAM3((short)3, "param3"); private static final Map<String, _Fields> byName = new HashMap<String, _Fields>(); static { for (_Fields field : EnumSet.allOf(_Fields.class)) { byName.put(field.getFieldName(), field); } } // 对象自己负责解码 public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { schemes.get(iprot.getScheme()).getScheme().read(iprot, this); } <pre name="code" class="java"> // 对象自己负责编码
public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
}
再来看XXXScheme类,这也是自动生成的,是方法参数XXX_args的内部类。
Scheme有两类,一个是StandardScheme,使用消息头+消息体的方式来编解码对象。一个是TupleScheme,直接采用写消息体的方式编解码,编码字节流更小。
拿demoMethod_argsStandardScheme举例,
1. 它的编码方法就是从writeStructBegin开始逐个写字段,每个字段写之前会writeFieldBegin开始,写字段类型和字段的顺序号。如果字段是一个类Struct,就调用这个类自己的编码方法write(TProtocol)。Thrift会给每个Struct生成类,这些类里面定义了这个类的编解码方法。最后写完之后以writeStructEnd结束
2. 它的解码方法从readStructBegin开始,然后读字段元数据readFieldBegin,读1个字节的字段类型,2个字段的字节顺序号,然后根据字段类型,来读相应类型长度的数据。直到读完用readStructEnd结束。
private static class demoMethod_argsStandardScheme extends StandardScheme<demoMethod_args> { public void read(org.apache.thrift.protocol.TProtocol iprot, demoMethod_args 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: // PARAM1 if (schemeField.type == org.apache.thrift.protocol.TType.STRING) { struct.param1 = iprot.readString(); struct.setParam1IsSet(true); } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; case 2: // PARAM2 if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { struct.param2 = new Parameter(); struct.param2.read(iprot); struct.setParam2IsSet(true); } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; case 3: // PARAM3 if (schemeField.type == org.apache.thrift.protocol.TType.MAP) { { org.apache.thrift.protocol.TMap _map0 = iprot.readMapBegin(); struct.param3 = new HashMap<String,String>(2*_map0.size); for (int _i1 = 0; _i1 < _map0.size; ++_i1) { String _key2; // required String _val3; // required _key2 = iprot.readString(); _val3 = iprot.readString(); struct.param3.put(_key2, _val3); } iprot.readMapEnd(); } struct.setParam3IsSet(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 struct.validate(); } public void write(org.apache.thrift.protocol.TProtocol oprot, demoMethod_args struct) throws org.apache.thrift.TException { struct.validate(); oprot.writeStructBegin(STRUCT_DESC); if (struct.param1 != null) { oprot.writeFieldBegin(PARAM1_FIELD_DESC); oprot.writeString(struct.param1); oprot.writeFieldEnd(); } if (struct.param2 != null) { oprot.writeFieldBegin(PARAM2_FIELD_DESC); struct.param2.write(oprot); oprot.writeFieldEnd(); } if (struct.param3 != null) { oprot.writeFieldBegin(PARAM3_FIELD_DESC); { oprot.writeMapBegin(new org.apache.thrift.protocol.TMap(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING, struct.param3.size())); for (Map.Entry<String, String> _iter4 : struct.param3.entrySet()) { oprot.writeString(_iter4.getKey()); oprot.writeString(_iter4.getValue()); } oprot.writeMapEnd(); } oprot.writeFieldEnd(); } oprot.writeFieldStop(); oprot.writeStructEnd(); } }
private static class demoMethod_argsTupleScheme extends TupleScheme<demoMethod_args> { @Override public void write(org.apache.thrift.protocol.TProtocol prot, demoMethod_args struct) throws org.apache.thrift.TException { TTupleProtocol oprot = (TTupleProtocol) prot; BitSet optionals = new BitSet(); if (struct.isSetParam1()) { optionals.set(0); } if (struct.isSetParam2()) { optionals.set(1); } if (struct.isSetParam3()) { optionals.set(2); } oprot.writeBitSet(optionals, 3); if (struct.isSetParam1()) { oprot.writeString(struct.param1); } if (struct.isSetParam2()) { struct.param2.write(oprot); } if (struct.isSetParam3()) { { oprot.writeI32(struct.param3.size()); for (Map.Entry<String, String> _iter5 : struct.param3.entrySet()) { oprot.writeString(_iter5.getKey()); oprot.writeString(_iter5.getValue()); } } } } @Override public void read(org.apache.thrift.protocol.TProtocol prot, demoMethod_args struct) throws org.apache.thrift.TException { TTupleProtocol iprot = (TTupleProtocol) prot; BitSet incoming = iprot.readBitSet(3); if (incoming.get(0)) { struct.param1 = iprot.readString(); struct.setParam1IsSet(true); } if (incoming.get(1)) { struct.param2 = new Parameter(); struct.param2.read(iprot); struct.setParam2IsSet(true); } if (incoming.get(2)) { { org.apache.thrift.protocol.TMap _map6 = new org.apache.thrift.protocol.TMap(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING, iprot.readI32()); struct.param3 = new HashMap<String,String>(2*_map6.size); for (int _i7 = 0; _i7 < _map6.size; ++_i7) { String _key8; // required String _val9; // required _key8 = iprot.readString(); _val9 = iprot.readString(); struct.param3.put(_key8, _val9); } } struct.setParam3IsSet(true); } } } }
方法返回值封装类的结构和方法参数封装类的结构和原理完全一致,这里不再赘述。