wire用户指南
基础
wire只有两个核心概念
- providers
- injectors
定义providers
wire中最主要的机制就是provider, 主要作用是用来提供一个对象, provider可以是一个函数并返回一个值.
1package foobarbaz
2
3type Foo struct {
4 X int
5}
6
7// ProvideFoo a Foo provider
8func ProvideFoo() Foo {
9 return Foo{X: 1}
10}
provider函数必须是导出的(public)以供其他包使用.
同时provider函数可以通过参数传入依赖.
1package foobarbaz
2
3type Bar struct {
4 Y int
5}
6
7// ProvideBar provide a Bar depends on Foo
8func ProvideBar(foo Foo) Bar {
9 return Bar{Y: foo.X + 1}
10}
并且provider函数可以返回一个错误.
1package foobarbaz
2
3type Baz struct {
4 Z int
5}
6
7func ProvideBaz(ctx context.Context, bar Bar) (Baz, error) {
8 if bar.Y < 0 {
9 return Baz{}, errors.New("can not provide baz, bar must be positive")
10 }
11 return Baz{Z: bar.Y + 2}, nil
12}
**将provider组织成一组依赖.**provider可以被放入一个依赖集合中, 在某些场景中非常有用,
要创建一组依赖集合需要使用wire.NewSet
函数. provider集合中的handler是顺序无关的, 所以
可以不用关注集合中handler的依赖关系. 并且集合是可以嵌套的.
1package foobarbaz
2
3import (
4 // ...
5 "github.com/google/wire"
6)
7
8var SuperSet = wire.NewSet(ProvideBaz, ProvideBar, ProvideFoo)
9var SuperSuperSet = wire.NewSet(SuperSet, ProvideOther)
injectors
一个应用程序使用injector将这些provider连接(wires up)起来. injector是一个将这些provider按依赖顺序
组织, 我们只需要提供injector的签名, 然后wire会通过代码生成的方式来实现具体的过程. 这种实现方式主要是
通过go语言提供的build tags特性(如果文件以//go:build wireinject
开头, build时没有指定tag的话该文件就不会参与构建)
然后在injector中调用wire.Build将需要依赖的providerSet或者provider们填进去, injector 的返回值也无关紧要只要是正确的类型就行了
1//go:build wireinject
2package main
3
4import (
5 "context"
6 "github.com/google/wire"
7 "foobarbaz"
8)
9
10func newBaz(ctx context.Context) (foobarbaz.Baz, error) {
11 wire.Build(foobarbaz.SuperSuperSet/*, xxx, xxx, xxx*/)
12 return foobazbar.Baz{}, nil
13}
高级特性
绑定接口
自然的, 依赖注入可以用来绑定一个接口的具体实现, wire通过类型标识选择一个符合接口类型的provider. 然而, 这并不是惯用法(idiomatic), go的最佳实践是返回一个具体的类型, 事实上我们可以定义一个接口如何绑定一个具体的类型.
1type Fooer interface {
2 Foo() string
3}
4type MyFooer string
5
6func (m *MyFooer) Foo() string {
7 return string(*m)
8}
9
10func provideMyFooer() *MyFooer {
11 m := "Hello"
12 return (*MyFooer)(&m)
13}
14
15type Bar string
16
17func provideBar(f Fooer) string {
18 return f.Foo()
19}
20
21var Set = wire.NewSet(
22 provideMyFooer,
23 wire.Bind(new(Fooer), new(*MyFooer)),
24 provideBar,
25)
wire.Bind
的第一个参数是需要用到的接口, 而第二个参数是实现这个接口的具体类型的指针.
任何一个包含接口绑定的集合必须至有一个提供实现了这个接口的provider.
结构体providers
结构体可以通过provider提供的值来构造, 使用wire.Struct
函数来构造一个结构体类型, 然后告诉injector那些字段应该被注入.
injector将填充每一个声明了的字段.
1type Foo int
2type Bar int
3
4func ProvideFoo() Foo {return 1}
5func ProvideBar() Bar {return 2}
6
7type FooBar struct {
8 MyFoo Foo
9 MyBar Bar
10}
11
12var Set = wire.Set(
13 ProvideFoo, ProvideBar,
14 wire.Struct(new(FooBar), "MyFoo", "MyBar"),
15)
这里第一个参数是要将值注入的类型的指针, 后面是需要注入的字段的名字, 如果需要将所有字段注入可以直接使用"*"
=>wire.Struct(new(FooBar), "*")
.
使用*
时如果要避免某个字段不被注入可以使用结构体标签wire: "-"
绑定值
如果要给一个字段绑定一个具体值的话可以使用wire.Value(Foo{X: 1})
, 对于接口的话使用wire.InterfaceValue(new(io.Reader), os.Stdin)
将结构体的某个字段作为provider
有时provider想要使用结构体的某个字段, 我们可以定义一个额外的provider, 但是这通常是不必要的, 可以使用wire.FieldsOf
.
wire.Build(provideFoo, wire.FieldsOf(new(Foo), "S"))
clean up 函数
如果一个provide创建的值需要在不用时销毁, 我们可以给provider的返回值添加一个clean up函数.
1func provideFile(log Logger, path Path) (*os.File, func(), error) {
2 f, err := os.Open(string(path))
3 if err != nil {
4 return nil, nil, err
5 }
6 cleanup = func() {
7 if err := f.Close(); err != nil {
8 log.Log(err)
9 }
10 }
11 return f, cleanup, nil
12}
如果我们使用injector时不想处理错误可以使用
1func injectFoo() Foo {
2 panic(wire.Build(ProvideFoo, ProvideBar/*...*/))
3}