前言
以下内容来自 Wire 官方文档,花了一天把英文的 readme 啃了遍,发现存在几个问题:
-
记住的不多
-
后面遇到问题需要再来看readme,但是个人英语阅读效率太低,又要花很多时间
-
将来复习时,去看英文文档,没有中文的直观。
所以在这里记录以下。
介绍
Wire是一个代码生成工具,使用依赖注入自动连接组件。组件之间的依赖关系在Wire中表示为函数参数,鼓励显式初始化,而不是全局变量。因为Wire没有运行时状态或反射,所以编写用于Wire的代码即使对于手工编写的初始化也很有用。
如果使用全局变量,可以在运行期间动态获取到依赖,但会导致依赖关系不固定。
使用示例
未使用依赖注入
模拟一个活动,让迎宾员用一条特定的信息向客人致意。
在本设计中,有三种结构类型:
// 为迎宾者发送的消息
type Message string
// 传达该消息的迎宾者
type Greeter struct {
Message Message
}
// 迎宾者说出问候语
func (g Greeter) Greet() Message {
return g.Message
}
// 以迎宾者问候客人开始的活动
type Event struct {
Greeter Greeter
}
// 活动开始
func (e Event) Start() {
msg := e.Greeter.Greet()
fmt.Println(msg)
}
相应的初始化函数
func NewMessage() Message {
return Message("Hi there!")
}
func NewGreeter(m Message) Greeter {
return Greeter{Message: m}
}
func NewEvent(g Greeter) Event {
return Event{Greeter: g}
}
主函数
func main() {
message := NewMessage()
greeter := NewGreeter(message)
event := NewEvent(greeter)
event.Start()
}
- 这是简单示例,如果是实际项目中,依赖关系复杂,这个main函数的内容很难写。
使用 Wire
使用依赖注入设计原则,传递每个组件所需的任何信息。有助于编写易于测试的代码,并且易于替换实现类,提高扩展性。
主函数
func main() {
e := InitializeEvent()
e.Start()
}
需要编写的 wire.go
//+build wireinject
func InitializeEvent() Event {
wire.Build(NewEvent, NewGreeter, NewMessage)
return Event{}
}
//+build wireinject
:此文件的目的是说明需要使用哪些提供provider函数,因此需要在文件顶部标识,使用构建约束将其从最终构建后的文件中排除
使用 wire 命令工具
安装
go get github.com/google/wire/cmd/wire
使用
wire
:生成依赖注入代码wire_gen.go
go generate
:当存在wire_gen.go
时,也就是非初次,还可使用这个命令
生成的依赖注入代码 wire_gen.go
func InitializeEvent() Event {
message := NewMessage()
greeter := NewGreeter(message)
event := NewEvent(greeter)
return event
}
支持返回异常
func NewEvent(g Greeter) (Event, error) {
if g.Grumpy {
return Event{}, errors.New("could not create event: event greeter is grumpy")
}
return Event{Greeter: g}, nil
}
主函数
func main() {
e, err := InitializeEvent()
if err != nil {
fmt.Printf("failed to create event: %s\n", err)
os.Exit(2)
}
e.Start()
}
依赖注入声明函数
func InitializeEvent() (Event, error) {
wire.Build(NewEvent, NewGreeter, NewMessage)
return Event{}, nil
}
生成的依赖注入代码
func InitializeEvent() (Event, error) {
message := NewMessage()
greeter := NewGreeter(message)
event, err := NewEvent(greeter)
if err != nil {
return Event{}, err
}
return event, nil
}
给依赖提供参数
需要参数的依赖
func NewMessage(phrase string) Message {
return Message(phrase)
}
依赖注入声明函数
func InitializeEvent(phrase string) (Event, error) {
wire.Build(NewEvent, NewGreeter, NewMessage)
return Event{}, nil
}
生成的依赖注入代码
func InitializeEvent(phrase string) (Event, error) {
message := NewMessage(phrase)
greeter := NewGreeter(message)
event, err := NewEvent(greeter)
if err != nil {
return Event{}, err
}
return event, nil
}
进阶使用
Provider 分组
如果将所有的 Provider 都用到一个依赖注入声明函数里,会很臃肿,不便于管理。可以根据包来对 Provider 进行分组,每个包下都有一个 ProviderSet,使包里的依赖注入更清晰。
子包下的 wire.go
var SuperSet = wire.NewSet(ProvideFoo, ProvideBar, ProvideBaz)
主包下的 wire.go
func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
wire.Build(foobarbaz.SuperSet)
return foobarbaz.Baz{}, nil
}
接口绑定实现类
接口
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(new(Fooer), new(*MyFooer)),
provideBar)
Struct Providers
不提供Struct 的 Provider,直接注入字段
type Foo int
type Bar int
func ProvideFoo() Foo {/* ... */}
func ProvideBar() Bar {/* ... */}
type FooBar struct {
MyFoo Foo
MyBar Bar
}
var Set = wire.NewSet(
ProvideFoo,
ProvideBar,
wire.Struct(new(FooBar), "MyFoo", "MyBar"))
注入所有字段
wire.Struct(new(FooBar), "*")
排除个别字段
type Foo struct {
mu sync.Mutex `wire:"-"`
Bar Bar
}
提供初始值
这是没有写 Provider 的情况,也可以通过 Provider 来指定初始值
type Foo struct {
X int
}
func injectFoo() Foo {
wire.Build(wire.Value(Foo{X: 42}))
return Foo{}
}
生成的依赖注入代码
func injectFoo() Foo {
foo := _wireFooValue
return foo
}
var (
_wireFooValue = Foo{X: 42}
)
- 这种方式指定初始值,不能调用任何函数,以及从管道里获取数据
给接口提供初始值
func injectReader() io.Reader {
wire.Build(wire.InterfaceValue(new(io.Reader), os.Stdin))
return nil
}
字段作为依赖
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 ""
}
使用 wire.FieldsOf()
func injectedMessage() string {
wire.Build(
provideFoo,
wire.FieldsOf(new(Foo), "S"))
return ""
}
生成的依赖注入代码
func injectedMessage() string {
foo := provideFoo()
string2 := foo.S
return string2
}
提供清理函数
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()
不 return 最终生成的对象
在依赖注入声明函数中,会返回一个无用对象,因为最终生成的依赖注入代码中,返回的不是你定义的那个对象。那怎样可以不写这个,但又不会编译错误呢?答案是抛异常。
func injectFoo() Foo {
panic(wire.Build(/* ... */))
}
最佳实践
Options Structs
如果一个对象依赖太对对象,Provider 的方法签名就很长。当然可以使用 Struct Provider,不过这样就不能做一些初始化操作(以前写在 Provider 里的)。封装参数可以解决这个问题。
type Options struct {
// Messages is the set of recommended greetings.
Messages []Message
// Writer is the location to send greetings. nil goes to stdout.
Writer io.Writer
}
func NewGreeter(ctx context.Context, opts *Options) (*Greeter, error) {
// ...
}
var GreeterSet = wire.NewSet(wire.Struct(new(Options), "*"), NewGreeter)
项目里遇到个有趣的问题,main函数里需要初始化grpc,并且还要执行一个依赖的方法,也就是想通过wire构建依赖的函数返回多个对象(一个是grpc,一个是需要后台运行函数的那个依赖对象)。
查了好久都没查到有用的信息,最终搜索英文才查到了官方回复
这不正是龙哥跟我说的解决办法吗,我两都认为这样不是很好😅