除了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) 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
|
package main
import ( "github.com/google/wire" )
func InitApp() (*App, error) { wire.Build(NewConfig, NewDB, NewApp) return &App{}, nil }
|
执行 wire
命令生成的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
package main
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), "*"))
type MenuSrv struct { MenuRepo *menu.MenuRepo MenuActionRepo *menu.MenuActionRepo MenuActionResourceRepo *menu.MenuActionResourceRepo }
func NewMenuSrv(...) MenuSrv{ ... }
|
接口绑定
wire
无法自动将具体实现与接口进行关联,我们需要显示声明它们之间的关联关系。
如下,在创建集合的时候,使用wire.Bind
将IRouter
和Router
进行绑定。
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
创建MenuSrvSet
和RoleSrvSet
两个集合,然后再用这两个集合创建第三个集合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, 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
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 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, 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)) }
|