Thriftgo 反射使用文档
简介
Thriftgo 在 v0.3.0 版本提供了 Thrift IDL 反射功能,可以在运行时获取 Thrift IDL 的相关信息与描述符,并支持通过描述符来找到对应的 Go Type 并进行反射操作。我们可以在运行时使用这些反射查询接口,获取到 IDL 信息,以便进行各种查询和操作,以及更轻松的使用反射。
快速开始
例如有这样一个 IDL
以 Kitex Tool 为例,在代码生成时,额外添加 -thrift with_reflection 参数,执行如下命令
生成代码的目录如下:
相比原有场景,会多出 demo-reflection.go 文件。这个文件包含了 demo.thrift 的 IDL 信息记录以及反射相关接口。
当在 Golang 代码里引入这个包的时候,该 IDL 的元信息就会被初始化并加载记录,在后续的代码中就可以开始使用 Thrift 反射的 API 了。
获取 Descriptor 与 IDL 元信息
我们可以通过 Descriptor 来获取 IDL 里对应的信息。例如通过 StructDescriptor,来获取 IDL 在结构体定义时的信息,以及拿到字段的注释和注解等等:
具体来说,Descriptor 的种类以及他们之间的关系如下:
这些 Descriptor 也是用 Thrift IDL 定义的:https://github.com/cloudwego/thriftgo/blob/main/descriptor.thrift
除了在代码生成里提供 Golang 结构体找到 Descriptor 的方法以外,thrift_reflection 包内也提供了按照 Golang 类型或者名称查找 Descriptor 的方式:
同时也附带了一些常用的工具函数来进行 Descriptor 的查找:
获取 GoTypes 与反射操作
Descriptor 也能和 Golang 反射类型配合使用,Descriptor 提供了 GetGoType 方法,用来获取对应的 Golang 类型:
类似的,除了 StructDescriptor,其他 Descriptor 也都有对应的 GoType 获取方法
要注意的是,Descriptor 查询到的信息是来自 IDL 的,名称和 Go 代码的结构体不一定完全一致,因为下划线转驼峰,或者重名避免策略,实际的 IDL 命名和 Golang 代码名称会有出入。例如某结构体的 Field 名叫 my_name,通过 Descriptor 拿到的 Name 为 my_name,但实际上 Golang 结构体里,这个 Field 是 MyName (下划线转驼峰命名)
Descriptor 也可以配合 Golang 反射 API 使用。虽然无法通过 GetFieldByName 来直接查找,但 Descriptor 里的 Field 的顺序是和 Golang 反射里的一致的,可以像下面这段代码一样通过 Descriptor 查找反射字段
另外 Thriftgo 也提供了一些简单的关于 Descriptor 与 Golang 反射结合使用的 API,例如下面这段代码,展示了通过 fieldDescriptor 来反射设置某个结构体的字段的值:
在 Thriftgo v0.3.0 版本中,由于 Thrift 反射功能刚刚发布,所以配合 Golang 反射使用的 API 封装较少,后续会根据用户建议与常用场景,完善这方面的 API。
FAQ
Q:为什么初始化阶段出现错误会直接采用 panic 来停止整个程序
因为若初始化与注册 IDL 时出现错误,导致某个 IDL 无法被注册上,那么在后续的使用里,会影响到 IDL 信息之间的查询结果(例如找不到某个本应存在的 Descriptor),可能会因此导致用户的业务逻辑出错。所以 IDL 注册出错时直接 panic,以明显的方式提示用户。
Q:初始化时报错 “thrift reflection: file ‘xxx’ is already registered”
因为 Thrift IDL 元信息在注册时,是以 IDL 文件的 filepath 为唯一的 Key 的,所以当遇到有重复名称的 IDL 注册时,会报这个提示。这个设计与 Google Protobuf 反射的注册重复报错是类似的。解决方式是根据报错提示的路径找到对应的文件,重新用不同的 IDL 名称再生成代码即可。
Q:考虑把反射信息的生成设置为默认行为吗
目前要生成反射信息,需要 thriftgo 额外开启参数。由于反射功能刚刚推出,可能在后续投入实际使用后可能会有修改,所以打算等该功能较为成熟后,再作为默认行为开启,从而尽可能在现阶段减少对不使用该功能的用户的打扰。
Q:这个功能可以和公共结构体一起使用吗
生成引用结构体 公共结构体功能是将本地某个 IDL 对应的 Golang 代码全部指向远端公共仓库的 Golang 代码。Thriftgo 反射功能可以和公共结构体功能一起使用。但有一些需要注意的说明。
首先,公共结构体的远端仓库在生成代码时也需要生成 Thriftgo 反射内容。
理论上,公共结构体场景下,本地的这份 IDL 是要和远端的 IDL 完全一致的。但不排除存在微弱的不一致情况,例如远端 IDL 多了一些结构体,或者某些结构体下增加了新的字段。例如下面的案例:
公共结构体场景生成的 Golang 代码为:
由于实际的 Golang 结构体是使用的远端的,所以在公共结构体场景下,这份 IDL 的元信息在反射注册时将以远端的 IDL 情况为准(也就是既有 A 结构体也有 B 结构体的那个 IDL)。
此外还有一种特殊情况目前无法支持(出现概率极小),当对远端的一个 IDL 进行了拆分,让本地的一份 IDL 对应到了远端的两个 IDL 时,这种场景无法成功注册反射信息。