用法
一般会在一个名为wire.go
的文件里提供需要自动注入的函数,在开头添加好tag
,在函数中使用wire.Build
方法,并提供创建依赖的方法,多个依赖可以组装为一个Set
。
示例
// +build wireinject // tag不能忘
var ormSet = wire.NewSet(mysql.Newengine)
var DataSourceSet = wire.NewSet(ormSet, japi.GetClient, cache.NewRedisCache)
func InitAuthorRepo() AuthorRepoInterface {
wire.Build(
NewAuthorRepo,
// 可以用这种方式替代New函数,特别是New的参数比较多时,*表示对结构体的全部字段进行生成
// wire.Struct(new(authorRepo), "*"),
// 如果NewAuthorRepo的返回值不是显示声明AuthorRepoInterface,wire会提示无AuthorRepoInterface的provider
// 此时就需要下面的语句进行绑定
// wire.Bind(new(AuthorRepoInterface), new(*authorRepo)),
repo.DataSourceSet,
)
return nil
}
特性
wire.Struct
type App struct {
Foo *Foo
Bar *Bar
NoInject int `wire:"-"`
}
该方法可以代替New函数,特别是New函数参数较多,不太好写的时候。
wire.Struct(new(App),"Foo","Bar")
生成指定的字段。
wire.Struct(new(App), "*")
表示结构体的全部字段都自动生成。
若是有某些字段不想在*时生成,可以给它添加tag。
wire.Bind
wire.Bind(new(AuthorRepoInterface), new(*authorRepo))
wire.Bind
可以指定结构体实现的接口,如果结构体的New函数返回值不是显示声明为其实现的接口,那么wire会报错,此时就需要使用Bind
方法进行绑定。
wire.Value
// provider.go
type Foo struct {
X int
}// wire.go
...
wire.Build(wire.Value(Foo{X: 42}))
...
虽不常见,但有时需要为基本类型的属性绑定具体值, 这时可以使用 wire.Value
wire.Build(wire.InterfaceValue(new(io.Reader), os.Stdin))
为接口类型绑定具体值,可以使用 wire.InterfaceValue
wire.FieldsOf
有时我们只是需要用某个对象的属性作为Provider,例如:
// provider
func provideBar(foo Foo)*Bar{
return foo.Bar
}
// injector
...
wire.Build(provideFoo, provideBar)
...
这时可以用 wire.FieldsOf
加以简化,省掉啰嗦的 provider 👉🏻
wire.Build(provideFoo,wire.FieldsOf(new(Foo), "Bar"))
与 wire.Struct
类似, wire.FieldsOf
也会自动适应指针/非指针的注入请求
清理函数
type App struct {
File *os.File
Conn net.Conn
}
func provideFile() (*os.File,func(), error) {
f, err := os.Open("foo.txt")
if err != nil {
return nil, nil, err
}
cleanup := func() {
if err := f.Close(); err != nil {
log.Println(err)
}
}
return f,cleanup, nil
}
func provideNetConn() (net.Conn,func(), error) {
conn, err := net.Dial("tcp", "foo.com:80")
if err != nil {
return nil, nil, err
}
cleanup := func() {
if err := conn.Close(); err != nil {
log.Println(err)
}
}
return conn,cleanup, nil
}
前面提到若provider 和 injector 函数有返回错误, 那么wire会自动处理。除此以外,wire还有另一项自动处理能力: 清理函数。
所谓清理函数是指型如 func()
的闭包, 它随provider生成的组件一起返回, 确保组件所需资源可以得到清理。
清理函数典型的应用场景是文件资源和网络连接资源👉🏻
//+build wireinject
package main
import "github.com/google/wire"
func NewApp() (*App, func(), error) {
panic(wire.Build(
provideFile,
provideNetConn,
wire.Struct(new(App), "*"),
))
}
上述代码定义了两个 provider 分别提供了文件资源和网络连接资源👉🏻
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package main
// Injectors from wire.go:
func NewApp() (*App, func(), error) {
file, cleanup, err := provideFile()
if err != nil {
return nil, nil, err
}
conn, cleanup2, err := provideNetConn()
if err != nil {
cleanup()
return nil, nil, err
}
app := &App{
File: file,
Conn: conn,
}
return app, func() {
cleanup2()
cleanup()
}, nil
}
注意由于provider 返回了清理函数, 因此injector函数签名也必须返回,否则将会报错👉🏻
func main() {
app, cleanup, err := NewApp()
if err != nil {
log.Fatal(err)
}
defer cleanup()
...
}
生成代码中有两点值得注意:
- 当
provideNetConn
出错时会调用cleanup()
, 这确保了即使后续处理出错也不会影响前面已分配资源的清理。 - 最后返回的闭包自动组合了
cleanup2()
和cleanup()
。 意味着无论分配了多少资源, 只要调用过程不出错,他们的清理工作就会被集中到统一的清理函数中。 最终的清理工作由injector的调用者负责
可以想像当几十个清理函数的组合在一起时, 手工处理上述两个场景是非常繁琐且容易出错的。 wire 的优势再次得以体现。
然后就可以使用了👉🏻
注意main函数中的 defer cleanup()
,它确保了所有资源最终得到回收