基础

Wire 有两个核心概念:providers 和 injectors.

Providers 定义

Wire 中的主要机制是 providers: 一个可以产生值的函数。这些函数是普通的 Go 代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package foobarbaz

type Foo struct {
    X int
}

// ProvideFoo returns a Foo.
func ProvideFoo() Foo {
    return Foo{X: 42}
}

Provider 函数必须导出,以便从其他包中使用,就像普通函数一样。

Provider 可以使用参数指定依赖项:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package foobarbaz

// ...

type Bar struct {
    X int
}

// ProvideBar returns a Bar: a negative Foo.
func ProvideBar(foo Foo) Bar {
    return Bar{X: -foo.X}
}

Providers 可以发返回错误:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package foobarbaz

import (
    "context"
    "errors"
)

// ...

type Baz struct {
    X int
}

// ProvideBaz returns a value if Bar is not zero.
func ProvideBaz(ctx context.Context, bar Bar) (Baz, error) {
    if bar.X == 0 {
        return Baz{}, errors.New("cannot provide baz when bar is zero")
    }
    return Baz{X: bar.X}, nil
}

Providers 可以分组到提 provider sets 中。如果几个 provider 经常一起使用,这很有用。要将这些提供程序添加到名为 SuperSet 的新集中,请使用 wire.NewSet 函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package foobarbaz

import (
    // ...
    "github.com/google/wire"
)

// ...

var SuperSet = wire.NewSet(ProvideFoo, ProvideBar, ProvideBaz)

您也可以将其他 provider sets 加到 provider set 中。

Injectors

应用程序使用 injector 连接这些 provider:按依赖顺序调用 provider 的函数。使用 Wire,写下 injector’s 的签名,然后 Wire 生成函数的主体。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// +build wireinject
// The build tag makes sure the stub is not built in the final build.

package main

import (
    "context"

    "github.com/google/wire"
    "example.com/foobarbaz"
)

func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
    wire.Build(foobarbaz.MegaSet)
    return foobarbaz.Baz{}, nil
}

像 provider 一样,injectors 可以在输入上参数化(然后发送给 provider),并且可以返回错误。wire.Build 参数与 wire.NewSet 一样:他们构成一个 provider 集合。这是在该 injector 注入器的生成代码期间使用的 provider 集。

在 injectors 的文件中发现的任何非 injector 声明将被复制到生成的文件中。

你可以在包目录中调用Wire来生成 injector:

wire

Wire 将产生一个实现 injector 调用方法名为 wire_gen.go 文件。看起来如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

import (
    "example.com/foobarbaz"
)

func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
    foo := foobarbaz.ProvideFoo()
    bar := foobarbaz.ProvideBar(foo)
    baz, err := foobarbaz.ProvideBaz(ctx, bar)
    if err != nil {
        return 0, err
    }
    return baz, nil
}

就像你看到的一样,输出非常接近开发人员自己编写的内容。此外,运行时对 Wire 的依赖性很小:所有编写的代码都只是普通的 Go 代码,可以在没有 Wire 的情况下使用。

创建 wire_gen.go 后,您可以通过运行 go generate 来重新生成它。

高级功能

以下特性都建立在 providers 和 injectors 概念的基础上。

绑定 Interfaces

依赖注入通常用于绑定接口的具体实现。Wire 通过类型标识将输入与输出匹配,因此倾向于创建一个返回接口类型的程序。然而,这并不是惯用的,因为 Go 的最佳实践是返回具体类型。相反,您可以在提供程序 providers sets 中声明接口绑定:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type Fooer interface {
    Foo() string
}

type MyFooer string

func (b *MyFooer) Foo() string {
    return string(*b)
}

func provideMyFooer() *MyFooer {
    b := new(MyFooer)
    *b = "Hello, World!"
    return b
}

type Bar string

func provideBar(f Fooer) string {
    // f will be a *MyFooer.
    return f.Foo()
}

var Set = wire.NewSet(
    provideMyFooer,
    wire.Bind((*Fooer)(nil), (*MyFooer)(nil)),
    provideBar)

wire.Bind 的第一个参数是指向所需接口类型值的指针,任何包含接口绑定的集合都必须在提供具体类型的同一集合中有一个 provides 程序。

如果需要,还可以将一个接口绑定到另一个接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type FooerPlus interface {
  Fooer
  Bar() String
}

func ProvideFooerPlus() FooerPlus {
  ...
}

var FooerPlusAsFooer = wire.NewSet(
  ProvideFooerPlus,
  wire.Bind(new(Fooer), *new(FooerPlus)))

结构体 Providers

结构体也可以作为 providers。Injector 将使用相应的 provider 填充每个字段,而不是调用函数。对于给定的结构类型 S,这将同时提供 S*S。例如,给定以下 providers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
type Foo int
type Bar int

func ProvideFoo() Foo {
    // ...
}

func ProvideBar() Bar {
    // ...
}

type FooBar struct {
    Foo Foo
    Bar Bar
}

var Set = wire.NewSet(
    ProvideFoo,
    ProvideBar,
    FooBar{})

FooBar 生成的注入器如下所示:

1
2
3
4
5
6
7
8
9
func injectFooBar() FooBar {
    foo := ProvideFoo()
    bar := ProvideBar()
    fooBar := FooBar{
        Foo: foo,
        Bar: bar,
    }
    return fooBar
}

同样,如果注射器需要 *FooBar

绑定值

有时,将基本值(通常为nil)绑定到类型是有用的。 您可以将值表达式添加到提 provider set,而不是让 injectors 依赖于一次性提供程序函数。

1
2
3
4
5
6
7
8
type Foo struct {
    X int
}

func injectFoo() Foo {
    wire.Build(wire.Value(Foo{X: 42}))
    return Foo{}
}

这个生成如下所示

1
2
3
4
5
6
7
8
func injectFoo() Foo {
    foo := _wireFooValue
    return foo
}

var (
    _wireFooValue = Foo{X: 42}
)

需要注意的是,表达式将被复制到注 injector 的包中;对变量的引用将在 injector 组件初始化期间进行求值。如果表达式调用任何函数或从任何通道接收,Wire 将发出错误。

对于接口值,使用 InterfaceValue

1
2
3
4
func injectReader() io.Reader {
    wire.Build(wire.InterfaceValue(new(io.Reader), os.Stdin))
    return nil
}

清理函数

如果 provides 创建了一个需要清理的值(例如,关闭一个文件),那么它可以返回一个闭包来清理资源。稍后如果在 injector 的实现中,调用的提供程序返回错误,注入器将使用它向调用方返回聚合清理函数或者清理资源。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func provideFile(log Logger, path Path) (*os.File, func(), error) {
    f, err := os.Open(string(path))
    if err != nil {
        return nil, nil, err
    }
    cleanup := func() {
        if err := f.Close(); err != nil {
            log.Log(err)
        }
    }
    return f, cleanup, nil
}

provider 清理函数必须保证在调用前这个清理函数在 provider 提供输入并且必须是匿名func()

替代 Injector 语法

如果你厌倦了在 injector 函数声明的末尾写 return foobarbaz.Foo{}, nil,可以使用 panic 用一个简洁的方式写出来:

1
2
3
func injectFoo() Foo {
    panic(wire.Build(/* ... */))
}