Wire
wire
是 Google 开源的一个依赖注入工具。它是一个代码生成器,并不是一个框架。我们只需要在一个特殊的go
文件中告诉wire
类型之间的依赖关系,它会自动帮我们生成代码,帮助我们创建指定类型的对象,并组装它的依赖。
安装
$ go install github.com/google/wire/cmd/wire@latest
两个概念
正式开始前需要先了解一下 wire 当中的两个概念:provider
和 injector
Provider
Provider
是一个普通的函数,这个函数会返回构建依赖关系所需的组件。如下所示,就是一个 provider
函数,在实际使用的时候,往往是一些简单的工厂函数,这个函数不会太复杂。
// NewPostRepo 创建文章 Repo
func NewPostRepo() IPostRepo {}
不过需要注意的是在 wire 中不能存在两个 provider 返回相同的组件类型
Injector
injector 也是一个普通函数,我们常常在 wire.go
文件中定义 injector
函数签名,然后通过 wire
命令自动生成一个完整的函数
//+build wireinject
func GetBlogService() *Blog {
panic(wire.Build(NewBlogService, NewPostUsecase, NewPostRepo))
}
第一行的 //+build wireinject
注释确保了这个文件在我们正常编译的时候不会被引用,而 wire .
生成的文件 wire_gen.go
会包含 //+build !wireinject
注释,正常编译的时候,不指定 tag 的情况下会引用这个文件
wire.Build
在 injector
函数中使用,用于表名这个 injector
由哪些 provider
提供依赖, injector
函数本身只是一个函数签名,所以我们直接在函数中 panic
实际生成代码的时候并不会直接调用 panic
示例
type Message string
func NewMessage() Message {
return Message("Hi there!")
}
type Greeter struct {
Message Message
}
func NewGreeter(m Message) Greeter {
return Greeter{Message: m}
}
func (g Greeter) Greet() Message {
return g.Message
}
type Event struct {
Greeter Greeter // <- adding a Greeter field
}
func NewEvent(g Greeter) Event {
return Event{Greeter: g}
}
func (e Event) Start() {
msg := e.Greeter.Greet()
fmt.Println(msg)
}
设计一个程序,其中 Event
依赖Greeter
,Greeter
依赖Message
如果我们手动构建Event
则需要
func main() {
message := NewMessage()
greeter := NewGreeter(message)
event := NewEvent(greeter)
event.Start()
}
使用wire生成代码
对于上面的NewEvent
、NewGreeter
、NewMessage
都是provider
我们只要再创建一个独立的文件 wrie.go
,并在里面创建一个Injector
即可
//go:build wireinject
// +build wireinject
package main
import "github.com/google/wire"
func InitializeEvent() Event {
panic(wire.Build(NewEvent, NewGreeter, NewMessage))
}
在执行完wire
命令之后就会生成wire_gen.go
,它的内容就是构建 Event
。我们需要连同wire_gen.go
一起提交到版本控制系统里
// 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 InitializeEvent() Event {
message := NewMessage()
greeter := NewGreeter(message)
event := NewEvent(greeter)
return event
}
此时我们就可以直接在main里这样用
func main() {
event := InitializeEvent()
event.Start()
}
返回错误
在 go 中如果遇到错误,我们会在最后一个返回值返回 error,wire 同样也支持返回错误的情况,只需要在 injector 的函数签名中加上 error 返回值即可
调整Provider的签名
type Event struct {
Greeter Greeter
}
//func NewEvent(g Greeter) Event {
// return Event{Greeter: g}
//}
func NewEvent(g Greeter) (Event, error) {
if time.Now().Unix()%2 == 0 {
return Event{}, errors.New("could not create event: event greeter is grumpy")
}
return Event{Greeter: g}, nil
}
func (e Event) Start() {
msg := e.Greeter.Greet()
fmt.Println(msg)
}
调整Injector的签名
func InitializeEvent() (Event, error) {
panic(wire.Build(NewEvent, NewGreeter, NewMessage))
}
生成的代码如下所示,可以发现会像我们自己写代码一样判断一下 if err
然后返回
// 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 InitializeEvent() (Event, error) {
message := NewMessage()
greeter := NewGreeter(message)
event, err := NewEvent(greeter)
if err != nil {
return Event{}, err
}
return event, nil
}
main中的调用
func main() {
e, err := InitializeEvent()
if err != nil {
fmt.Printf("failed to create event: %s\n", err)
os.Exit(2)
}
e.Start()
}
传入参数
调整Provider的签名
type Message string
//func NewMessage() Message {
// return Message("Hi there!")
//}
func NewMessage(phrase string) Message {
return Message(phrase)
}
调整Injector的签名
func InitializeEvent(phrase string) (Event, error) {
panic(wire.Build(NewEvent, NewGreeter, NewMessage))
}
不展示生成的代码了,main中的调用
func main() {
event, err := InitializeEvent("Hi there!")
if err != nil {
fmt.Printf("failed to create event: %s\n", err)
os.Exit(2)
}
event.Start()
}
ProviderSet
有时候可能多个类型有相同的依赖,我们每次都将相同的构造器传给wire.Build()
不仅繁琐,而且不易维护,一个依赖修改了,所有传入wire.Build()
的地方都要修改。为此,wire
提供了一个ProviderSet
(构造器集合),可以将多个构造器打包成一个集合,后续只需要使用这个集合即可。
如下,后续再调整NewGreeter
和NewMessage
,就可以统一改了
package main
import "github.com/google/wire"
var wireSet = wire.NewSet(NewGreeter, NewMessage)
func InitializeEvent(phrase string) (Event, error) {
panic(wire.Build(wireSet, NewEvent))
}
func InitializeEvent2(phrase string) (Event, error) {
panic(wire.Build(wireSet, NewEvent))
}
结构构造器
对于Greeter
前面使用NewGreeter
作为Provider
type Greeter struct {
Message Message
}
func NewGreeter(m Message, phrase string) Greeter {
return Greeter{Message: m}
}
如果不显式实现NewGreeter
,可以直接使用wire
提供的结构构造器(Struct Provider)。结构构造器创建某个类型的结构,然后用参数或调用其它构造器填充它的字段
结构构造器使用wire.Struct
注入,第一个参数固定为new(结构名)
,后面可接任意多个参数,表示需要为该结构的哪些字段注入值
我们也可以使用通配符*
表示注入所有字段
var wireSet = wire.NewSet(NewMessage, wire.Struct(new(Greeter), "Message"))
func InitializeEvent(phrase string) (Event, error) {
panic(wire.Build(wireSet, NewEvent))
}
结构字段作为构造器
有时候我们编写一个构造器,只是简单的返回某个结构的一个字段,这时可以使用wire.FieldsOf
简化操作。
type Foo struct {
S string
N int
F float64
}
func getS(foo Foo) string {
// Bad! Use wire.FieldsOf instead.
return foo.S
}
func provideFoo() Foo {
return Foo{ S: "Hello, World!", N: 1, F: 3.14 }
}
func injectedMessage() string {
wire.Build(
provideFoo,
getS)
return ""
}
func injectedMessage() string {
wire.Build(
provideFoo,
wire.FieldsOf(new(Foo), "S"))
return ""
}
同样的,第一个参数为new(结构名)
,后面跟多个参数表示将哪些字段作为构造器,*
表示全部。
绑定值
有时候,我们需要为某个类型绑定一个值,而不想依赖构造器每次都创建一个新的值。有些类型天生就是单例,例如配置,数据库对象(sql.DB
)。这时我们可以使用wire.Value
绑定值,使用wire.InterfaceValue
绑定接口
修改一下 Greeter
使他依赖一个 int 和 io.Reader 然后为它直接绑定 a=10
io.Reader = os.Stdin
// main.go
var singletonMessage = NewMessage("Hello, world!")
type Message string
func NewMessage(phrase string) Message {
return Message(phrase)
}
type Greeter struct {
a int
r io.Reader
Message Message
}
func NewGreeter(m Message, phrase string) Greeter {
return Greeter{Message: m}
}
func (g Greeter) Greet() Message {
return g.Message
}
var wireSet = wire.NewSet(
wire.Struct(new(Greeter), "*"),
wire.Value(10),
wire.InterfaceValue(new(io.Reader), os.Stdin),
wire.Value(singletonMessage),
)
func InitializeEvent(phrase string) (Event, error) {
panic(wire.Build(wireSet, NewEvent))
}
绑定接口
使用 wire.Bind
将 Struct
和接口进行绑定,表示这个结构体实现了这个接口,wire.Bind
的使用方法就是 wire.Bind(new(接口), new(实现))
func NewGreeter(m Message, phrase string) Greeter {
return Greeter{Message: m}
}
func (g Greeter) Greet() Message {
return g.Message
}
type IGreeter interface{
Greet() Message
}
type Event struct {
Greeter IGreeter
}
var wireSet = wire.NewSet(
wire.Struct(new(Greeter), "*"),
wire.Value(10),
wire.InterfaceValue(new(io.Reader), os.Stdin),
wire.Value(singletonMessage),
)
func InitializeEvent(phrase string) (Event, error) {
panic(wire.Build(wireSet, NewEvent, wire.Bind(new(IGreeter), new(*Greeter))))
}