wire 是 Go 官网推出的一款相似于 Spring 依赖注入工具。有别于以往的依赖注入工具 facebookgo/inject、uber-go/dig 等,采纳反射实现。wire 采纳通过代码形容对象之间的依赖关系,而后主动生成代码在编译期实现依赖注入的工具
源码:https://github.com/google/wire
什么是依赖注入
说到依赖注入(Dependency Injection, 缩写 DI),不得不提管制反转(Inversion of Control,缩写为 IoC)。IoC 是一种设计思维,核心作用是升高代码耦合度。
传统零碎利用是在类外部被动援用对象,从而导致类与类之间高度耦合,不利于保护,而有了 IoC 容器后,把创立和查找对象工作交给容器,由容器动静的将某个依赖关系注入对象中,控制权由调用者利用代码转移到 IoC 容器, 控制权产生了反转,从而实现对象间解耦。依赖注入是实现 IoC 解决依赖问题的设计模式。
举例
type TestA struct {B *TestB// 依赖}
type TestB struct {
}
func NewA() *TestA {
return &TestA{B:new(TestB),
}
}
下面代码,TestA 依赖 TestB, 这样当前退出须要对 TestB 批改时,还须要对 TestA 做批改。这样做有以下几个问题:
代码耦合性高不利于保护这种依赖关系
不利于性能复用,TestA 无奈复用示例好的 TestB
不不便单元测试
为此咱们能够对代码以依赖注入形式批改
type TestA struct {B *TestB}
type TestB struct {
}
func NewA(b *TestB) *TestA {
return &TestA{B:b,}
}
这样,咱们将初始化后的 TestB 注入到 NewA 中了,解耦了局部依赖。
以上的依赖注入形式,在代码少,零碎不简单时实现起来没问题,当零碎宏大到肯定程序时就力不从心了。怎么解决呢?这里就须要着重介绍的 wire 依赖注入工具了。
wire 应用办法
装置
go install github.com/google/wire/cmd/wire@latest
并确保将 $GOPATH/bin 其增加到您的 $PATH.
根本
Wire 有两个外围概念:providers 和 injectors。
providers 示例
在 foobarbaz 目录下创立文件 foobarbaz.go,内容如下
package foobarbaz
import (
"context"
"errors"
)
type Foo struct {X int}
// ProvideFoo returns a Foo.
func ProvideFoo() Foo {return Foo{X: 42}
}
type Bar struct {X int}
// ProvideBar returns a Bar: a negative Foo.
func ProvideBar(foo Foo) Bar {return Bar{X: -foo.X}
}
type Baz struct {X int}
// ProvideBaz returns a value if Bar is not zero.
func ProvideBaz(ctx context.Context, bar Bar) (Baz, error) {
if bar.X == 0 {return Baz{}, errors.New("cannot provide baz when bar is zero")
}
return Baz{X: bar.X}, nil
}
injectors:创立 wire.go 文件(文件名能够不是 wire,但个别是这个)
示例
// +build wireinject
// The build tag makes sure the stub is not built in the final build.
package main
import (
"context"
"demo/foobarbaz"
"github.com/google/wire"
)
func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {wire.Build(foobarbaz.ProvideFoo,foobarbaz.ProvideBar,foobarbaz.ProvideBaz)
return foobarbaz.Baz{}, nil}
留神:须要在文件头部减少构建束缚://+build wireinject
执行 wire 主动生成依赖代码,能够间接用 wire 或者用 wire gen wire.go 来生成 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
import (
"context"
"demo/foobarbaz"
)
// Injectors from wire.go:
func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {foo := foobarbaz.ProvideFoo()
bar := foobarbaz.ProvideBar(foo)
baz, err := foobarbaz.ProvideBaz(ctx, bar)
if err != nil {return foobarbaz.Baz{}, err
}
return baz, nil
}
通过以上代码,能够看到主动生成的代码蕴含了 error 解决,跟手动写的代码简直一样。
wire.go 文件头部 //+build wireinject,+build 其实是 Go 语言的一个个性,确保在 go build 编译时不解决此文件。
wire_gen.go 文件头部 // +build !wireinject,其中的!wireinject 是来通知 wire 命令不解决此文件。
高级个性
NewSet
NewSet 个别利用在初始化对象比拟多的状况下,缩小 Injector 外面的信息。
var SuperSet = wire.NewSet(ProvideFoo, ProvideBar, ProvideBaz)
wire.Build(SuperSet)
Struct
除了函数能够作为 Provider 外,stuct 也能够作为 Provider。对于生成的构造类型 S,wire.Struct 同时提供 S 和 *S。
示例
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"))
生成的注入器 FooBar 如下所示:
func injectFooBar() FooBar {foo := ProvideFoo()
bar := ProvideBar()
fooBar := FooBar{
MyFoo: foo,
MyBar: bar,
}
return fooBar
}
第一个参数 wire.Struct 是指向所需构造类型的指针,后续参数是要注入的字段的名称。一个非凡的字符串 ”“ 能够用作通知注入器注入所有字段的快捷方式。所以 wire.Struct(new(FooBar), ““)产生与下面雷同的后果。
对于下面的示例,您能够 ”MyFoo” 通过更改为 Set 来指定仅注入:
var Set = wire.NewSet(
ProvideFoo,
wire.Struct(new(FooBar), "MyFoo"))
那么生成的注入器 FooBar 将如下所示:
func injectFooBar() FooBar {foo := ProvideFoo()
fooBar := FooBar{MyFoo: foo,}
return fooBar
}
如果注入器返回 a*FooBar 而不是 a FooBar,生成的注入器将如下所示:
func injectFooBar() *FooBar {foo := ProvideFoo()
fooBar := &FooBar{MyFoo: foo,}
return fooBar
}
有时避免某些字段被注入器填充是很有用的,尤其是在传递 * 给 wire.Struct. 您能够标记一个字段,wire:"-"
让 Wire 疏忽这些字段。例如:
type Foo struct {
mu sync.Mutex `wire:"-"`
Bar Bar
}
当您应用 提供 Foo 类型时 wire.Struct(new(Foo), “*”),Wire 将主动省略该 mu 字段。此外,像在 wire.Struct(new(Foo), “mu”).
Bind
Bind 函数的作用是为了让接口类型的依赖参加 Wire 的构建。Wire 的构建依附参数类型,接口类型是不反对的。Bind 函数通过将接口类型和实现类型绑定,来达到依赖注入的目标。
type Foo struct {X int}
func injectFoo() Foo {wire.Build(wire.Value(Foo{X: 42}))
return Foo{}}
CleanUp
如果提供者创立了一个须要清理的值(例如敞开文件),那么它能够返回一个闭包来清理资源。如果稍后在注入器实现中调用的提供者返回谬误,注入器将应用它向调用者返回聚合清理函数或清理资源。
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
}
清理函数保障在任何提供者输出的清理函数之前被调用,并且必须具备签名 func()。
备用注入器语法
如果你厌倦了 return foobarbaz.Foo{}, nil 在注入器函数申明的开端写,你能够用一个更简洁的形式来写 panic:
func injectFoo() Foo {panic(wire.Build(/* ... */))
}
留神问题
果我的依赖关系图有两个雷同类型的依赖关系怎么办?
Wire 不容许在提供给 的提供者的传递闭包中存在一个类型的多个提供者 wire.Build,因为这通常是一个谬误。对于须要雷同类型的多个依赖项的非法状况,您须要创造一种新类型来调用此其余依赖项。
type Foo struct {/* ... */}
type Bar struct {/* ... */}
func newFoo1() *Foo { /* ... */}
func newFoo2() *Foo { /* ... */}
解决办法
type Foo1 Foo
type Foo2 Foo
func newFoo1() *Foo1 { /* ... */}
func newFoo2() *Foo2 { /* ... */}
总结
wire 通过程序主动生成跟手动写一样代码,没有应用低效的反射,效率高。
如果不小心遗记了某个 provider,wire 会报出具体的谬误,帮忙开发者迅速定位问题。
links
https://github.com/google/wire
https://juejin.cn/post/714801…