仍在迭代中,仅供测试。请勿应用于生产环境!!!
go-decorator
是实现了 Go 语言装饰器特性的编译链工具。
使用该工具,通过 //go:decor decoratorfunctionName
来注释函数,即可使用装饰器decoratorfunctionName
,快速完成样板代码注入、改变函数行为、控制逻辑流程等。
装饰器的使用场景,可以类比其他语言,比如 Python、TypeScript。
go-decorator
是在编译时进行的装饰器注入,因此它不会破坏你项目的源文件,也不会额外在项目中生成新的.go
文件和其他多余文件。 和 go:generate
生成式完全不同。
使用样例
创建一个项目,我们这里module名为 demo
.
在你的项目引入装饰器依赖(go.mod 项目):
$ go mod init
$ go get -u github.com/dengsgo/go-decorator
创建 main.go
, 编写如下代码:
package main
import (
"github.com/dengsgo/go-decorator/decor"
"log"
)
func main() {
// 正常调用你的函数。
// 由于这是一个声明使用装饰器logging的函数,
// decorator 编译链会在编译代码时注入装饰器方法logging的调用。
// 所以使用上面的方式编译后运行,你会得到如下输出:
//
// 2023/08/13 20:26:30 decorator function logging in []
// 2023/08/13 20:26:30 this is a function: myFunc
// 2023/08/13 20:26:30 decorator function logging out []
//
// 而不是只有 myFunc 本身的一句输出。
// 也就是说通过装饰器改变了这个方法的行为!
myFunc()
}
// 通过使用 go:decor 注释声明该函数将使用装饰器logging来装饰。
//
//go:decor logging
func myFunc() {
log.Println("this is a function: myFunc")
}
// 这是一个普通的函数
// 但是它实现了 func(*decor.Context) 类型,因此它还是一个装饰器方法,
// 可以在其他函数上使用这个装饰器。
// 在函数中,ctx 是装饰器上下文,可以通过 ctx 获取到目标函数的出入参
// 和目标方法的执行。
// 如果函数中没有执行 ctx.TargetDo(), 那么意味着目标函数不会执行,
// 即使你代码里调用了被装饰的目标函数!这时候,目标函数返回的都是零值。
// 在 ctx.TargetDo() 之前,可以修改 ctx.TargetIn 来改变入参值。
// 在 ctx.TargetDo() 之后,可以修改 ctx.TargetOut 来改变返回值。
// 只能改变出入参的值。不要试图改变他们的类型和数量,这将会引发运行时 panic !!!
func logging(ctx *decor.Context) {
log.Println("decorator function logging in", ctx.TargetIn)
ctx.TargetDo()
log.Println("decorator function logging out", ctx.TargetOut)
}
然后 go install
安装 decorator
工具:
$ go install github.com/dengsgo/go-decorator/cmd/decorator@latest
运行 decorator
,显示 decorator
版本信息即为安装成功。
$ decorator
decorator 0.1.0 beta , https://github.com/dengsgo/go-decorator
decorator
是 go
的编译链工具,依靠 go
命令来调用它运行,进行代码的编译。
所以我们编译代码需要在 go build
命令中加入 -toolexec decorator
参数。
编译&&运行:
$ go build -toolexec decorator
$ ./demo
会有类似输出打印:
2023/08/13 20:26:30 decorator function logging in []
2023/08/13 20:26:30 this is a function: myFunc
2023/08/13 20:26:30 decorator function logging out []
说明 go-decorator
已经成功加入了编译链,并注入了装饰器。
详细的使用文档可以访问下面的链接:
Guide | Guide 国内Gitee
项目代码:
GitHub: github.com/dengsgo/go-…
有一些大家可能会关心的点,这里也提一下:
条件和限制
以下几种情况需要注意:
- 使用装饰器的目标函数范围仅限当前项目内。依赖的其他库即使使用
//go:decor
也无法被装饰。
例如,你的项目module名称是 a/b/c
,那么 //go:decor
只在 a/b/c
及其子包中生效(a/b/c/d
有效,a/m/
无效)。
但是//go:decor
可以使用任意包的装饰器,没有范围限制。
- 不能在同一个目标函数上同时使用相同的装饰器重复装饰;
- 不能对装饰器函数应用装饰器;
- 升级
decorator
后或者调整编译参数可能需要在 go 命令中追加-a
参数强制编译一次,以覆盖旧的编译缓存。
开发与调试
decorator
作为 go 编译链中的一环,编译时被 go 编译器加载使用。它与 go 的编译链保持兼容,不会产生副作用。
开发流程中要改变的只是给用到的 go 命令增加 -toolexec decorator
参数,其他完全一致,感觉不到有变化。
你也可以随时取消这个参数。放弃项目对 go 装饰器的使用。即使代码中保留了 //go:decor
注释也不会有任何副作用(因为它对于标准工具链来说只是无意义的注释而已)。
调试同理。
例如,在 vscode 中,编辑 launch.json
:
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch file",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${file}",
"buildFlags": "-toolexec decorator"
}
]
}
添加 "buildFlags": "-toolexec decorator"
这一行以启用 decorator
的装饰器编译。
然后正常断点调试即可。
调试体验会不断完善,如果发现问题请让我知道 Issues。
性能
尽管 decorator
在编译时会对目标函数做额外的处理,但它仅仅只构建必要的上下文参数,没有额外开销,更没有反射。相对于原始go代码直接调用装饰器函数来讲,性能几乎是一致的。
最后
项目初期,可能会遇到各种各样的问题,随时欢迎 Issues 反馈给我, Issues 。
最后最后,强调一下,现阶段不要将它用于生产环境!现阶段不要将它用于生产环境!现阶段不要将它用于生产环境!