Wire 等依赖注入工具旨在简化初始化代码的管理
💡 依赖注入 是一种标准技术,用于生成灵活且松散耦合的代码,通过显式地为组件提供它们工作所需的所有依赖项。
Wire 介绍#
参考文章:Golang依赖注入框架wire使用详解_魂影魔宅-CSDN博客_golang wire
Wire 有两个基本概念:提供者(provider)和注入器(Injector)。
官方的定义:
provider: a function that can produce a value. These functions are ordinary Go code.
injector: a function that calls providers in dependency order.
With Wire, you write the injector’s signature, then Wire generates the function’s body.
- 提供者(provider):一个可以产生值的函数(通常是构造函数)。这些函数都是普通的 Go 代码
- 注入器(Injector):一个按依赖顺序调用提供者的函数。
使用 Wire,您编写注入器(Injector)的签名,然后 Wire 生成函数的主体。
provider#
provider
就是普通的Go函数,可以把它看作是某对象的构造函数,我们通过provider
告诉wire
该对象的依赖情况:
1
2
3
4
5
6
7
8
9
10
11
| // NewUserStore是*UserStore的provider,表明*UserStore依赖于*Config和 *mysql.DB.
func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}
// NewDefaultConfig是*Config的provider,没有依赖
func NewDefaultConfig() *Config {...}
// NewDB是*mysql.DB的provider,依赖于ConnectionInfo
func NewDB(info ConnectionInfo) (*mysql.DB, error) {...}
// UserStoreSet 可选项,可以使用wire.NewSet将通常会一起使用的依赖组合起来。
var UserStoreSet = wire.NewSet(NewUserStore, NewDefaultConfig)
|
injector#
injector
是wire
生成的函数,我们通过调用injector
来获取我们所需的对象或值,injector会按照依赖关系,按顺序调用provider
函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // File: wire_gen.go// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
// initUserStore是由wire生成的injector
func initUserStore(info ConnectionInfo) (*UserStore, error) {
// *Config的provider函数
defaultConfig := NewDefaultConfig()
// *mysql.DB的provider函数
db, err := NewDB(info)
if err != nil {
return nil, err
}
// *UserStore的provider函数
userStore, err := NewUserStore(defaultConfig, db)
if err != nil {
return nil, err
}
return userStore, nil
}
|
injector
帮我们把按顺序初始化依赖的步骤给做了,我们在main.go
中只需要调用initUserStore
方法就能得到我们想要的对象了。
那么wire
是怎么知道如何生成injector
的呢?我们需要写一个函数来告诉它:
- 定义
injector
的函数签名 - 在函数中使用
wire.Build
方法列举生成injector
所需的provider
1
2
3
4
5
6
7
| 例如:
// initUserStore用于声明injector的函数签名
func initUserStore(info ConnectionInfo) (*UserStore, error) {
// wire.Build声明要获取一个UserStore需要调用到哪些provider函数
wire.Build(UserStoreSet, NewDB)
return nil, nil // 这些返回值wire并不关心。
}
|
有了上面的函数,wire
就可以得知如何生成injector
了。wire
生成injector
的步骤描述如下:
- 确定所生成
injector
函数的函数签名:func initUserStore(info ConnectionInfo) (*UserStore, error)
- 感知返回值第一个参数是
UserStore
- 检查
wire.Build
列表,找到UserStore
的provider:NewUserStore
- 由函数签名
func NewUserStore(cfg *Config, db *mysql.DB)
得知NewUserStore
依赖于Config
, 和mysql.DB
- 检查
wire.Build
列表,找到Config
和mysql.DB
的provider:NewDefaultConfig
和NewDB
- 由函数签名
func NewDefaultConfig() *Config
得知Config
没有其他依赖了。 - 由函数签名
func NewDB(info *ConnectionInfo) (*mysql.DB, error)
得知mysql.DB
依赖于ConnectionInfo
。 - 检查
wire.Build
列表,找不到ConnectionInfo
的provider
,但在injector函数签名中发现匹配的入参类型,直接使用该参数作为NewDB
的入参。 - 感知返回值第二个参数是
error
- 按依赖关系,按顺序调用
provider
函数,拼装injector
函数。
接口绑定#
💡 根据依赖倒置原则(Dependence Inversion Principle),对象应当依赖于接口,而不是直接依赖于具体实现。
wire
中如何处理接口依赖:
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
31
32
33
34
35
36
37
38
39
40
41
42
43
| // UserService
type UserService struct {
userRepo UserRepository// <-- UserService依赖UserRepository接口
}
type User struct {
ID int
Name string
}
// UserRepository 存放User对象的数据仓库接口,比如可以是mysql,restful api.
type UserRepository interface {
// GetUserByID 根据ID获取User, 如果找不到User返回对应错误信息
GetUserByID(id int) (*User, error)
}
// NewUserService *UserService构造函数
func NewUserService(userRepo UserRepository) *UserService {
return &UserService{
userRepo:userRepo,
}
}
// mockUserRepo 模拟一个UserRepository实现
type mockUserRepo struct {
foo string
bar int
}
// GetUserByID UserRepository接口实现
func (u *mockUserRepo) GetUserByID(id int) (*User,error){
return &User{}, nil
}
// NewMockUserRepo *mockUserRepo构造函数
func NewMockUserRepo(foo string,bar int) *mockUserRepo {
return &mockUserRepo{
foo:foo,
bar:bar,
}
}
// MockUserRepoSet 将 *mockUserRepo与UserRepository绑定
var MockUserRepoSet = wire.NewSet(NewMockUserRepo,wire.Bind(new(UserRepository), new(*mockUserRepo)))
|
- 在这个例子中,
UserService
依赖UserRepository
接口,其中mockUserRepo
是UserRepository
的一个实现,由于在Go的最佳实践中,更推荐返回具体实现而不是接口。所以mockUserRepo
的provider
函数返回的是*mockUserRepo
这一具体类型。wire
无法自动将具体实现与接口进行关联,我们需要显示声明它们之间的关联关系。通过wire.NewSet
和wire.Bind
将*mockUserRepo
与UserRepository
进行绑定:
1
2
| // MockUserRepoSet 将 *mockUserRepo与UserRepository绑定
var MockUserRepoSet = wire.NewSet(NewMockUserRepo,wire.Bind(new(UserRepository), new(*mockUserRepo)))
|
定义injector函数签名:
1
2
3
| func InitializeUserService(foo string, bar int) *UserService{
wire.Build(NewUserService,MockUserRepoSet)// 使用MockUserRepoSetreturn nil
}
|
wire
对provider
的返回值个数和顺序有所规定:
- 第一个参数是需要生成的依赖对象
- 如果返回2个返回值,第二个参数必须是
func()
或者error
- 如果返回3个返回值,第二个参数必须是
func()
,第三个参数则必须是error