thrift-enhancer是一组支持thrift协议的加强包,设计的初衷是为了简化thrift的调用。传统的thrift会使用thrift的编译器 编译出一组代码,然后基于这组代码进行rpc编程,编译出的代码往往和组织的代码风格不同,阅读起来很不方便。更有甚者,有时候 业务中可能已经存在一组类足以描述rpc的请求或者response,但为了使用thrift又编译一组类,同时需要一组转换的工具类,使得 代码膨胀厉;传统的thrift没有实现协议转换,比如 json与thrift的协议转换,这在网关应用中很重要;我们要替换传统的thrift 调用,那么transport的功能也应该是必不可少的了。thrift-enhancer提供了如下能力:
- thrift-stuff: pojo转出成thrift二进制协议的能力,这一点与protostuff 类似, 目前实现了thrift的二进制和压缩协议
- thrift-translator: 提供动态解析idl并生成参数对象的能力,动态生成的参数对象可以自动转换为thrift协议数据,同时提供 thrift与json、xml的双向转换, 动态解析idl功能基于antlr4)实现
- thrift-client: 提供thrift调用的能力,目前仅支持 TSocket的能力
<dependency>
<groupId>com.github.rolandhe</groupId>
<artifactId>thrift-stuff</artifactId>
<version>1.0.0</version>
</dependency>
compile group: 'com.github.rolandhe', name: 'thrift-stuff', version: '1.0.0'
public enum PojoAdStyle {
WORD,
IMAGE;
}
public class PojoStandardAd {
private byte type; // required
private short category; // required
private int id; // required
private long seq; // required
private String name; // required
private byte[] content; // required
private boolean isStart; // required
private double percent; // required
private PojoAdStyle adStyle; // required
public byte getType() {
return type;
}
public void setType(byte type) {
this.type = type;
}
public short getCategory() {
return category;
}
public void setCategory(short category) {
this.category = category;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public long getSeq() {
return seq;
}
public void setSeq(long seq) {
this.seq = seq;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
public boolean getIsStart() {
return isStart;
}
public void setStart(boolean start) {
isStart = start;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PojoStandardAd that = (PojoStandardAd) o;
return type == that.type &&
category == that.category &&
id == that.id &&
seq == that.seq &&
isStart == that.isStart &&
Double.compare(that.percent, percent) == 0 &&
Objects.equals(name, that.name) &&
Arrays.equals(content, that.content) &&
adStyle == that.adStyle;
}
@Override
public int hashCode() {
int result = Objects.hash(type, category, id, seq, name, isStart, percent, adStyle);
result = 31 * result + Arrays.hashCode(content);
return result;
}
public double getPercent() {
return percent;
}
public void setPercent(double percent) {
this.percent = percent;
}
public PojoAdStyle getAdStyle() {
return adStyle;
}
public void setAdStyle(PojoAdStyle adStyle) {
this.adStyle = adStyle;
}
}
public static PojoStandardAd createPojoAd(int id) {
PojoStandardAd pojoStandardAd = new PojoStandardAd();
pojoStandardAd.setType((byte)101);
pojoStandardAd.setCategory((short)32500);
pojoStandardAd.setId(id);
pojoStandardAd.setSeq(Long.MAX_VALUE - 1L);
pojoStandardAd.setName("testPojoThrift");
pojoStandardAd.setContent(content);
pojoStandardAd.setStart(true);
pojoStandardAd.setPercent(99.32D);
pojoStandardAd.setAdStyle(PojoAdStyle.IMAGE);
return pojoStandardAd;
}
public static void main(String[] args) {
PojoStandardAd pojoStandardAd = createPojoAd(Integer.MAX_VALUE);
byte[] pojoBuffer = PojoBinarySerializer.encode(pojoStandardAd);
// 反序列化
PojoStandardAd decodePojo = PojoBinarySerializer.decode(PojoStandardAd.class,pojoBuffer);
System.out.println(decodePojo.equals(pojoStandardAd));
}
thrift idl支持map, list, set, 但java的泛型是语法糖,在编译后会丢失泛型信息,因此需要注解来描述泛型类型。
描述list field
- value()用于指定list element对应的类
- realListClass() 用于反序列化时构建java List对象的实现类,可以指定ArrayList,LinkedList等
类似 ListContainer, 用于指定set 类型
描述 map field
- keyClass(), 指定map key的类型
- valueClass() 指定map value类型
- realMapClass(), 反序列化时用于指定java Map的实现类,比如HashMap,TreeMap等
对应thrift optional,描述指定的field是可选的
public class PojoCreative {
private long id;
@ListContainer(PojoStandardAd.class)
private List<PojoStandardAd> standardList;
@ListContainer(Byte.class)
private List<Byte> byteList;
@ListContainer(Short.class)
private List<Short> shortList;
@ListContainer(Integer.class)
private List<Integer> intList;
@ListContainer(Long.class)
private List<Long> longList;
@MapContainer(keyClass = String.class,valueClass = String.class)
private Map<String,String> mapString;
@MapContainer(keyClass = String.class,valueClass = PojoStandardAd.class)
private Map<String,PojoStandardAd> mapAd;
@MapContainer(keyClass = Integer.class,valueClass = String.class)
@Optional
private Map<Integer,String> intPair;
private PojoStandardAd spec;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public List<PojoStandardAd> getStandardList() {
return standardList;
}
public void setStandardList(List<PojoStandardAd> standardList) {
this.standardList = standardList;
}
public List<Byte> getByteList() {
return byteList;
}
public void setByteList(List<Byte> byteList) {
this.byteList = byteList;
}
public List<Short> getShortList() {
return shortList;
}
public void setShortList(List<Short> shortList) {
this.shortList = shortList;
}
public List<Integer> getIntList() {
return intList;
}
public void setIntList(List<Integer> intList) {
this.intList = intList;
}
public List<Long> getLongList() {
return longList;
}
public void setLongList(List<Long> longList) {
this.longList = longList;
}
public Map<String, String> getMapString() {
return mapString;
}
public void setMapString(Map<String, String> mapString) {
this.mapString = mapString;
}
public Map<String, PojoStandardAd> getMapAd() {
return mapAd;
}
public void setMapAd(
Map<String, PojoStandardAd> mapAd) {
this.mapAd = mapAd;
}
public Map<Integer, String> getIntPair() {
return intPair;
}
public void setIntPair(Map<Integer, String> intPair) {
this.intPair = intPair;
}
public PojoStandardAd getSpec() {
return spec;
}
public void setSpec(PojoStandardAd spec) {
this.spec = spec;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PojoCreative that = (PojoCreative) o;
return id == that.id &&
Objects.equals(standardList, that.standardList) &&
Objects.equals(byteList, that.byteList) &&
Objects.equals(shortList, that.shortList) &&
Objects.equals(intList, that.intList) &&
Objects.equals(longList, that.longList) &&
Objects.equals(mapString, that.mapString) &&
Objects.equals(mapAd, that.mapAd) &&
Objects.equals(intPair, that.intPair) &&
Objects.equals(spec, that.spec);
}
@Override
public int hashCode() {
return Objects
.hash(id, standardList, byteList, shortList, intList, longList, mapString, mapAd, intPair,
spec);
}
}
static PojoCreative createPojoCreative(){
PojoCreative pojoCreative = new PojoCreative();
pojoCreative.setId(Long.MAX_VALUE - 1L);
pojoCreative.setStandardList(Arrays.asList(createPojoAd(1),createPojoAd(2),createPojoAd(3)));
pojoCreative.setByteList(Arrays.asList((byte)0,(byte)32,(byte)101,(byte)127));
pojoCreative.setShortList(Arrays.asList((short)0,(short)32,(short)101,(short)32767));
pojoCreative.setIntList(Arrays.asList(0,32,101,Integer.MAX_VALUE));
pojoCreative.setLongList(Arrays.asList(0L,32L,101L,Long.MAX_VALUE));
Map<String,String> stringMap = new HashMap<>();
stringMap.put("a","1");
stringMap.put("b","2");
pojoCreative.setMapString(stringMap);
Map<String,PojoStandardAd> pojoStandardAdMap = new HashMap<>();
pojoStandardAdMap.put("x",createPojoAd(100));
pojoStandardAdMap.put("y",createPojoAd(200));
pojoCreative.setMapAd(pojoStandardAdMap);
Map<Integer,String> integerStringMap = new HashMap<>();
pojoCreative.setIntPair(integerStringMap);
pojoCreative.setSpec(createPojoAd(1001));
return pojoCreative;
}
public static void main(String[] args) {
PojoCreative pojoCreative = createPojoCreative();
byte[] pojoBuffer = PojoBinarySerializer.encode(pojoCreative);
// 反序列化
PojoCreative decodePojo = PojoBinarySerializer.decode(PojoCreative.class,pojoBuffer);
System.out.println(decodePojo.equals(pojoCreative));
}
上面的例子都是实现的TSerializer的功能,rpc调用时 thrift的 struct会被封装成一个message的结构, 而且需要提供一次调用的id,该id会被rpc调用完成后被原封不动的返回,可以用调用时的id和返回id进行核对 信息的正确性。FunctionCallContext描述rpc方法的一次调用。
打包rpc 调用 message
解析rpc返回message
实现了 thrift compact协议
日常业务中大家会在内部服务中使用thrift,而与外部的交流中会使用http/https,这时就需要一个称之为"gateway"的系统
把内部服务thrift方法暴露成http/https, 在gateway中如果针对每个thrift接口都开发协议转换或者调用thriftsdk的话,
那开发的工作量就比较巨大,而且容易引起jar包冲突,此时的理想状态就是使用一个json/xml到thrift的协议转换工具。而
thrift-translator就是这样的工具,而该转换工具仅仅使用thrift idl即可,不使用其他工具。
maven导入:
<!-- https://mvnrepository.com/artifact/com.github.rolandhe/thrift-translator -->
<dependency>
<groupId>com.github.rolandhe</groupId>
<artifactId>thrift-translator</artifactId>
<version>1.0.0</version>
</dependency>
gradle 导入:
// https://mvnrepository.com/artifact/com.github.rolandhe/thrift-translator
compile group: 'com.github.rolandhe', name: 'thrift-translator', version: '1.0.0'
thrift是使用idl来描述接口文档的,thrift idl主要包含 enum、struct、service 3中主要对象(由于java没有union,所以我们也暂时不支持union),
我们可以把thrift的idl文件解析出来,使用java 对象把enum、struct、service描述出来,这样就在一个抽象的级别
构建出thrift对象,比如struct就是一个 field(name/type/optional)的列表,而struct的实例就是一个field/value的列表。
antlr4是一个构建编译器的工具,它使用类似bnf的语言来描述语言的词法和语法,然后就能构建出这门语言的编译器,按照thrift的语法可以
定义对应的thrift的bnf(resources/Thrift.g4)。使用thrift的语法编译器就可以解析thrift的idl,由此能够动态实例化struct或者
thrift service方法的参数。
使用jackson的sax功能可以遍历json string,然后按照解析出的thrift struct就能够完成json field与thrift struct field的映射,
从而完成json与thrift的互转,动态thrift struct实例使用thrift-stuff序列化成byte数组。
xml的转换与json类似,使用jackson xml组件完成。
entity.thrift
namespace java com.github.rolandhe.thrift.enhancer.test
enum AdStyle
{
WORD,
IMAGE
}
struct StandardAd
{
1: i8 type,
2: i16 category,
3: i32 id,
4: i64 seq,
5: string name,
6: binary content,
7: bool isStart,
8: double percent,
9: AdStyle adStyle
}
struct Creative {
1: i64 id,
2: list<StandardAd> standardList,
3: list<i8> byteList,
4: list<i16> shortList,
5: list<i32> intList,
6: list<i64> longList,
7: map<string,string> mapString,
8: map<string,StandardAd> mapAd,
9: optional map<i32, string> intPair,
10: StandardAd spec
}
struct CreativeAll {
1: i64 id,
2: list<StandardAd> standardList,
3: list<i8> byteList,
4: list<i16> shortList,
5: list<i32> intList,
6: list<i64> longList,
7: map<string,string> mapString,
8: map<string,StandardAd> mapAd,
9: optional map<i32, string> intPair,
10: StandardAd spec,
11: i8 type,
12: i16 category,
13: i32 shortid,
14: i64 seq,
15: string name,
16: binary content,
17: bool isStart,
18: double percent,
19: AdStyle adStyle
}
complex.thrift
include "entity.thrift"
namespace java com.github.rolandhe.thrift.enhancer.test
service CreativeService
{
void create(1: entity.Creative creative),
map<string,string> convert(1: entity.Creative creative),
list<i32> getList(),
entity.StandardAd build(),
map<i32,string> work(),
bool show()
entity.AdStyle getStyle()
}
public static void main(String[] args) {
PojoStandardAd pojoStandardAd = createPojoAd(Integer.MAX_VALUE);
// convert to json
String json = JsonHelper.toJson(pojoStandardAd);
ResourceStreamIdlParser resourceStreamIncludeParser = new ResourceStreamIdlParser();
// load thrift idl file from resources
ThriftJavaIdl thriftJavaIdl = resourceStreamIncludeParser.parse("complex.thrift");
JsonTranslator jsonTranslator = new JsonTranslator();
byte[] transBuffer = jsonTranslator
.translateRequest(json, thriftJavaIdl, "entity.StandardAd", false);
}
更多示例参见 unit test case。
IdlParser 接口定义了idl文件的解析功能,AbstractIdlParser 实现了具体的解析实现,你只需要提供一个
idl文件的流即可,即你需要实现openInputStream方法,基于AbstractIdlParser你可以解析存储在任意地方
的idl 文件,比如: 你可以从web下载一个idl文件并解析。我们已经内置了:
- ResourceStreamIdlParser, 支持从resources加载idl文件
- FileIdlParser, 从绝对路径读取idl文件解析
- JsonTranslator, 实现json与thrift的转换
- XmlTranslator, 实现xml与thrift的转换