除了wire,Go的依赖注入框架还有Uber的dig和Facebook的inject,它们都是使用反射机制来实现运行时依赖注入(runtime dependency injection),而wire则是采用代码生成的方式来达到编译时依赖注入(compile-time dependency injection)。使用反射带来的性能损失倒是其次,更重要的是反射使得代码难以追踪和调试。而wire生成的代码是符合程序员常规使用习惯的代码,十分容易理解和调试。

为什么要使用wire

我们先来看一段代码,分析依赖注入的好处

main.go

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

import "fmt"

func main() {
conf := NewConfig()
db := NewDB(conf) // DB 依赖 Config
result := db.Find()
fmt.Println(result)
}

server.go

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

type Config struct {
DbSource string
}

func NewConfig() *Config {
return &Config{
DbSource: "root:root@tcp(127.0.0.1:3306)/test_db",
}
}

type DB struct {
table string
}

func NewDB(cfg *Config) *DB {
return &DB{table:"test_table"}
}

func (db *DB) Find() string {
return "db info string"
}

调用步骤如下:

  • 首先用 NewConfig 获取 Config 资源
  • 然后 NewDB 获取 DB 资源,这里需要注入 Config 的资源
  • 所以这里的 NewDB 依赖 NewConfig

使用依赖注入后的代码

wire.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//go:build wireinject
// +build wireinject

package main

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

// 调用wire.Build方法传入所有的依赖对象以及构建最终对象的函数得到目标对象
func InitApp() (*App, error) {
wire.Build(NewConfig, NewDB, NewApp) // NewConfig等三个函数都是Provider
return &App{}, nil // 这里返回值没有实际意义,只需符合函数签名即可,生成的 wire_gen.go 会帮你包装该值
}

执行 wire 命令生成的代码

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

//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package main

// Injectors from wire.go:

func InitApp() (*App, error) {
config := NewConfig()
db := NewDB(config)
app := NewApp(db)

Provider & Injector

provider(构造器)和injector(注入器)是wire的两个核心概念。

通过提供provider函数,让wire知道如何产生这些依赖对象。wire根据我们定义的injector函数签名,生成完整的injector函数,injector函数是最终我们需要的函数,它将按依赖顺序调用provider

Provider

provider就是普通的Go函数,可以把它看作是某对象的构造函数,我们通过provider告诉wire该对象的依赖情况。

如下:直接在wire.build里加入provider函数(NewMenuSrv)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type MenuSrv struct {
MenuRepo *menu.MenuRepo
MenuActionRepo *menu.MenuActionRepo
MenuActionResourceRepo *menu.MenuActionResourceRepo
}

func NewMenuSrv(...) MenuSrv{
...
}

func BuildWireInject() (*Injector, func(), error) {
wire.Build(
NewMenuSrv,
...
...
)
return new(Injector), nil, nil
}

结构构造器

结构构造器创建某个类型的结构,然后用参数或调用其它构造器填充它的字段,如下使用wire.Struct,第一个参数固定为new(结构名),后面可接任意多个参数,表示需要为该结构的哪些字段注入值,也可以使用通配符*表示注入所有字段。

1
2
3
4
5
6
7
8
9
10
11
var MenuSrvSet = wire.NewSet(wire.Struct(new(MenuSrv), "*"))
// 或者var MenuSrvSet = wire.NewSet(NewMenuSrv)
type MenuSrv struct {
MenuRepo *menu.MenuRepo
MenuActionRepo *menu.MenuActionRepo
MenuActionResourceRepo *menu.MenuActionResourceRepo
}

func NewMenuSrv(...) MenuSrv{
...
}

接口绑定

wire无法自动将具体实现与接口进行关联,我们需要显示声明它们之间的关联关系。

如下,在创建集合的时候,使用wire.BindIRouterRouter进行绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var RouterSet = wire.NewSet(wire.Struct(new(Router), "*"), wire.Bind(new(IRouter), new(*Router)))

type IRouter interface {
Register(app *gin.Engine) error
Prefixes() []string
}

type Router struct {
LoginApi *api.LoginAPI
MenuApi *api.MenuApi
RoleApi *api.RoleApi
UserApi *api.UserApi
}

func (r *Router) Register(app *gin.Engine) error {
return nil
}

func (r *Router) Prefixes() []string {
return []string{
"/api/",
}
}

ProviderSet

有时候可能多个类型有相同的依赖,我们每次都将相同的构造器传给wire.Build()不仅繁琐,而且不易维护,一个依赖修改了,所有传入wire.Build()的地方都要修改。为此,wire提供了一个ProviderSet(构造器集合),可以将多个构造器打包成一个集合,后续只需要使用这个集合即可。

如下:首先使用wire.NewSet创建MenuSrvSetRoleSrvSet两个集合,然后再用这两个集合创建第三个集合ServiceSet,ServiceSet是由MenuSrvSet、RoleSrvSet等多个provider组成的集合,使用wire.build的时候可以直接使用这个集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var MenuSrvSet = wire.NewSet(wire.Struct(new(MenuSrv), "*"))
var RoleSrvSet = wire.NewSet(wire.Struct(new(RoleSrv), "*"))
// ...

var ServiceSet = wire.NewSet(
MenuSrvSet,
RoleSrvSet,
UserSrvSet,
LoginSrvSet,
)

func BuildWireInject() (*Injector, func(), error) {
wire.Build(
service.ServiceSet,
...
...
...
)
return new(Injector), nil, nil
}

清理函数

构造器可以提供一个清理函数,如果后续的构造器返回失败,前面构造器返回的清理函数都会调用:

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
28
29
30
func InitGormDB() (*gorm.DB, func(), error) {
.....
cleanFunc := func() {} // 清理函数
return db, cleanFunc, nil
}

// 第二个返回值是清理函数
func BuildWireInject() (*Injector, func(), error) {
wire.Build(
InitGormDB,
.....
InjectorSet,
)
return new(Injector), nil, nil
}


func main() {
// injector 依赖注入容器,返回清理函数
injector, injectorCleanFunc, err := app.BuildWireInject()
if err != nil {
return nil, err
}

injector.GinEngine.Run(fmt.Sprintf("%s:%d", config.C.HTTP.Host, config.C.HTTP.Port))

return func() {
injectorCleanFunc() // 执行清理函数
}, nil
}

Injector

injectorwire生成的函数,我们通过调用injector来获取我们所需的对象或值,injector会按照依赖关系,按顺序调用provider函数:

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
var InjectorSet = wire.NewSet(wire.Struct(new(Injector), "*"))

// 初始化后最终生成的对象
type Injector struct {
GinEngine *gin.Engine
}

func BuildWireInject() (*Injector, func(), error) {
wire.Build(
dao.RepoSet,
service.ServiceSet,
...
InjectorSet,
)
return new(Injector), nil, nil
}

func main() {
// injector 依赖注入容器
injector, injectorCleanFunc, err := app.BuildWireInject()
if err != nil {
return nil, err
}

injector.GinEngine.Run(fmt.Sprintf("%s:%d", config.C.HTTP.Host, config.C.HTTP.Port))
}