Thrift 是有 Facebook 开源的一套 RPC 框架, 支持多种语言,它是通过自身的 中间语言(IDL),并借助代码生成引擎来生成各种主流语言的代码模板
Thrift 采用IDL(Interface Definition Language)来定义通用的服务接口,然后通过Thrift提供的编译器,可以将服务接口编译成不同语言编写的代码,通过这个方式来实现跨语言的功能。
IDL 中有基本类型、结构体类型、容器类型、枚举类型、异常类型、服务类型等六种类型。熟悉了这些常用类型后基本上可以应付日常开发。
类似于 C 语言中的结构体。在 Java 中就是 POJO,struct 类型有以下几个要求:
例如:
struct User{
1: required string name, //改字段必须填写
2: optional i32 age = 0; //默认值
3: bool gender //默认字段类型为optional
}
例如:
struct Test {
1: map<string, User> usermap,
2: set<i32> intset,
3: list<double> doublelist
}
Thrift不支持枚举类嵌套,枚举常量必须是32位的正整数。
例如:
enum HttpStatus {
OK = 200,
NOTFOUND=404
}
异常在语法和功能上类似于结构体,不过使用的关键字是 exception。对应 Java 的 Exception
例如:
exception MyException {
1: i32 errorCode,
2: string message
}
即我们需要提供的服务接口。
例如:
service HelloService {
i32 sayInt(1:i32 param)
string sayString(1:string param)
bool sayBoolean(1:bool param)
void sayVoid()
}
在 Java 中我们可能还会经常用到常量类型,在 IDL 中 常亮类型可以使用 const 关键字。
例如:
const i32 const_int = 1;
IDL 中的命名空间即是 Java 中的包名
例如:
namespace java com.example.test
会被转换成:
package com.example.test
IDL 支持 单行注释和多行注释
/**
* 这里是多行注释
*
*/
// 这里是单行注释
类似于 Java 中的 import 在本文件中引入其他文件中定义的内容。
注意:thrift文件名要用双引号包含,末尾没有逗号或者分号。
例如:
include "test.thrift"
...
struct HelloTest {
1: in32 uid;
...
}
由于 Thrift 需要使用代码生成引擎来将 Thrift 代码转换成其他语言(Java)的代码,所以需要在本地安装一下 Thrift
注意:本地安装的 Thrift 版本最好和生产环境保持一致。不然很可能会出现一些问题
使用以下命令安装:
brew install thrift
本人使用的是 Mac OS ,你和可以去 Thrift 官网,下载对应的安装包进行安装。
最终,如果你在命令行下执行下面的命令,出现 Thrift 版本号,就说明安装成功了。
> thrift -version
Thrift version 0.12.0
IDEA 默认是不支持 Thrift 所以需要安装 Thrift Support
插件。
安装好插件,重启后就能在 File -> new -> Project...
菜单下 看到 Thrift 工程了。
创建完成 Thrift 项目后,还需要配置一下 Thrift 代码生成引擎相关的东西。
打开菜单 File - > Project Structure... -> Project Settings -> Facets
,选择 Thrift
然后会让你代码输出的位置。
新建 HelloService.thrift
,在菜单中可能找不到创建 thrift 文件的菜单,直接新建 File 然后以 thrift 作为文件后缀就可以。
内容如下:
namespace java com.zdran.test.thrift
service ThriftHelloService{
string sayHello(1:string userName)
}
在这个接口文件中我们定义了一个 sayHello 的接口,它接收一个 String 类型的参数,并返回一个 String 类型的值。
现在我需要编译该 thrift 文件,然后就会自动生成我们需要的代码。
我们可以直接在控制台执行以下命令。
thrift -gen java HelloService.thrift
然后就能在gen-java
目录中看到生成后的 java 文件了。
还有一种办法,就是将 thrift 编译器添加到右键菜单里,直接在 IDEA 里编译。
打开 Preferences...
菜单,设置好你的 Thrift 的安装目录,然后勾选上下面的这个选项,就可以在 IDEA 里直接编译 thrift 文件了。
注意:thrift 文件 必须要在 src 目录下,或者手动设置的 source 目录(IDEA显示蓝色的文件夹)下才会有 Recompile 菜单
PS: 搞了半天,最后可以编译了,结果报错:
Error:Internal error: (java.util.concurrent.ExecutionException) com.intellij.util.xmlb.XmlSerializationException: Cannot deserialize class com.intellij.plugins.thrift.config.ThriftCompilerOptions
java.util.concurrent.ExecutionException: com.intellij.util.xmlb.XmlSerializationException: Cannot deserialize class com.intellij.plugins.thrift.config.ThriftCompilerOptions
... ...
也不知道是 IDEA 的问题,还是插件的问题,网上搜了一下啊,貌似没有遇到这个问题的,有时间再研究吧,直接在控制台编译好,然后我们再新建一个 Maven 项目。并添加 thrift 依赖
<dependencies>
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.12.0</version>
</dependency>
</dependencies>
然后把上面生成的 java 文件复制到 对应的包下面。
然后我再创建一个实现类ThriftHelloServiceImpl.java
,来实现我们定义好的接口。内容如下:
package com.test.thrift;
import org.apache.thrift.TException;
public class ThriftHelloServiceImpl implements ThriftHelloService.Iface {
@Override
public String sayHello(String userName) throws TException {
return "hello "+ userName;
}
}
我们这里采用 Main 方法的形式来进行客户端与服务端的通信,即启动两个 Main 方法,分别表示服务端与客户端。
首先创建服务端的 Main 方法,代码如下:
public class ServiceMain {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9090);
TServerSocket serverTransport = new TServerSocket(serverSocket);
ThriftHelloService.Processor processor =
new ThriftHelloService.Processor<ThriftHelloService.Iface>(new ThriftHelloServiceImpl());
TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();
TSimpleServer.Args tArgs = new TSimpleServer.Args(serverTransport);
tArgs.processor(processor);
tArgs.protocolFactory(protocolFactory);
TServer tServer = new TSimpleServer(tArgs);
System.out.println("启动 Thrift 服务端");
tServer.serve();
}
}
然后再创建客户端的 Main 方法,代码如下:
public class ClientMain {
public static void main(String[] args) {
TTransport transport = null;
try {
transport = new TSocket("127.0.0.1", 9090, 6000);
TProtocol protocol = new TBinaryProtocol(transport);
ThriftHelloService.Client client = new ThriftHelloService.Client(protocol);
transport.open();
String result = client.sayHello("thrift-1");
System.out.println(result);
} catch (TException e) {
e.printStackTrace();
} finally {
if (null != transport) {
transport.close();
}
}
}
}
然后启动服务端的 Main 方法,再启动客户端的 Main 方法,会看到 hello thrift-1,说明 RPC 通信成功了。
可能你会很奇怪,我们仅仅在 .thrift 文件中定义了一个接口,但是在生成后的 .java 文件中,却生成了大量的代码。
其实我们没必要太过关注生成后的代码,主要关注下面几个东西就可以了。
服务端关注以下两个接口类:
这个是服务端提供同步调用的接口。就是说,你实现的这个接口下面的方法,都会采用同步的方式调用。
同样,如果客户端和服务端需要采用异步的方式通信,服务端就需要实现 AsyncIface 下的接口。
同样,消费端也有两个 Client 一个是同步调用的,一个是异步调用的。
ThriftHelloService.Client
执行同步调用。
ThriftHelloService.AsyncClient
执行异步调用。