背景本文介绍 gengo 工具的 golang 代码生成技术,以及基于此完成的 golang annotation 插件。
代码生成的技术在各种语言中都很常用,尤其是静态语言,利用代码生成的技术可以实现一些大幅提高生产效率的工具。
比如 Java 中的 Annotation Lombok 会在Javac 解析成抽象语法树之后(AST), Lombok 根据自己的注解处理器,动态的修改 AST,增加新的节点(所谓代码),最终通过分析和生成字节码,根据具体的 Annotation 生成 Class 的 Getter、Setter 方法等,降低开发者的工作量。
广义上讲 C++ 的模版方法也是类似的代码生成技术,有时候使用生成代码技术不仅仅是出于降低工程量的角度,由于避免了运行时的自省调用,利用代码生成完成的功能往往执行效率也更好,比如 ffjson。
我们在另一篇文章中介绍的 protobuf 代码生成 也是一种常见的代码生成工具。利用 protoc 的生成工具,可以生成各种语言的代码,rpc server, client 模版代码,配合各种插件还能生成文档、脚本、Http 网关代码等。
Gengogengo 是 kubernetes 项目中常用的代码生成工具,kubernetes 项目中大量使用了这个工具用于代码生成。 gengo 更多的设计为一个比较通用的代码生成工具,完成代码表达树解析,生成的工作。
在 kubernetes 中的使用
code-generator 是对 gengo 的一层包装,完成 kubernetes 中常见的一些代码生成任务,比如 客户端代码生成、deepcopy 类代码生成等等,大部分是围绕 kubernetes api 对象的生成工具。
原理
Gengo 的目标是完成一个方便用户自行实现各种代码生成工具的库,他完成了几项工作
- 解析代码文件,解析完成的对象为 package、type
- 定义生成文件的工作模板,即 generator interface,开发者只需要简单实现其中的函数,就可以完成解析代码的大部分工作
- 渲染辅助工具,如 importer、namer 分别完成生成代码的 import 语句生成、type 渲染等功能。
gengo 代码导读
实际实现的插件要实现这个 interfacevar (...)const xxxfunc init(){}import (name "path/to/pkg")(c *Context) ExecutePackage(outDir string, p Package)
实战实战目标
使用过 Java 开发项目的同学一定对 java 中的 annotation 系统印象深刻,让我们来看一段代码。
面向切面AOPIOC控制反转
由于类似的概念实在是太好用了,在 go 语言中,很多先行者也做了一些尝试,比如针对 IOC 的 facebook inject、uber dig、google wire 、go-spring, 其中 inject、 dig 和 go-spring 都是基于 reflect 的,受制于 golang 的反射能力,代码中并不能做到像 Java 中那么智能,注入之前还是需要先手动提供一些构建方法,不是那么方便,wire 基于代码生成,风格和 Java 中差别比较大。
那么我们能不能利用 gengo 实现一套 annotation 系统,实现类似 Java 中的注解功能呢,如果实现了这个,那么 用它来实现 IOC 只是其中的一个用例插件。
Go-Annotation
实战代码在 go-annotation
对照 Java 的 Annotation 系统,一个 Annotation 比较关注的两个点:
- Retention:是 runtime 还是仅仅是 编译时使用,runtime 就忽略了,这点 golang 可以只关注 runtime 类型,也就是所有的 annotation 信息都会在 运行时暴露,以简化设计
- Target:注解使用的 对象范围是什么 是 类型、字段、方法、参数、还是本地变量、包 ?对于 golang 而言,最紧缺的能力在于 类型 和 方法的注解,字段的注解因为 golang 的提供 tag 能力结合 reflect 包,可以解决大部分问题。所以第一个版本,我们只关注 target 为 type、package、method 的三种类型。
Annotation 系统具体设计
Annotation@Annotation名字=AnnotationBody@
Template() stringTemplate() string
内置插件 Component 设计
Component 插件实现类似 Java 中的依赖注入能力。比如下面的 定义。
我们希望 创建 ComponentA 的时候
Singletonautowired
例如, 用 Annotation 系统实现的内置插件 Component, 实现了类似 Java 中的依赖注入功能, 具体使用请参考 examples/example_test.go
差不多了,这基本上是一个可以使用的 并且实现了 内置 IOC 插件的 Annotation 系统了,当然这才是个开始,很多好用的插件还可以继续实现。
欢迎关注这个项目的进展 go-annotation。
参考