Fx is the backbone of nearly all Go services at Uber.
fx和wire选择
wire每次启动前需要手动使用CLI编译才能调用依赖注入.这在启动时保证了更好的性能.
fx则是在启动时进行依赖注入,因此启动性能不会很好,但是在启动完成后运行时的性能是无影响的.
在考虑到业务对于启动时时长并不敏感,且wire如果新增/删除依赖对象需要再次执行cli生成,最终选择了fx.
基础函数介绍
fx.Provide
用于提供需要的依赖对象.这个函数不会一定执行,只有当有调用此依赖对象时才会被执行.
package main
import (
"fmt"
"go.uber.org/fx"
)
type A struct{}
func NewA() A {
fmt.Println("A is running")
return A{}
}
type B struct {
}
func NewB(p A) B {
fmt.Println("B is running")
return B{}
}
func main() {
app := fx.New(
fx.Provide(NewA),
fx.Provide(NewB),
)
app.Run()
}
A函数提供了1,如果B不依赖A函数返回的1,那么A函数就不会执行.
注意: 此时B没有被引用,因此只是将对应类型告知给了container,并没有实例化对象
// 控制台输出如下,并没有打印 running
[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1()
[Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm()
[Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm()
[Fx] PROVIDE main.A <= main.NewA()
[Fx] PROVIDE main.B <= main.NewB()
[Fx] RUNNING
fx.Invoke
此函数提供的对象一定会被执行,如果你有一个函数没有被依赖,但是必须要他被执行,那就使用Invoke
// 此时新增C类型
type C struct {
}
func NewC(b B) C {
fmt.Println("C is running")
return C{}
}
// 再将main修改成这样
func main() {
app := fx.New(
fx.Provide(
NewA,
NewB,
),
fx.Invoke(NewC),
)
app.Run()
}
如果没有Invoke C那么Provde的A,B两个函数就不会被执行. 但是现在C一定会被执行,且依赖1,2.那么A和B也就会被执行.
// 控制台输出如下,可以看到是根据依赖自动实例化对象的
[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1()
[Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm()
[Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm()
[Fx] PROVIDE main.A <= main.NewA()
[Fx] PROVIDE main.B <= main.NewB()
[Fx] INVOKE main.NewC()
[Fx] BEFORE RUN provide: main.NewA()
[Fx] RUN provide: main.NewA() in 0s
[Fx] BEFORE RUN provide: main.NewB()
[Fx] RUN provide: main.NewB() in 0s
[Fx] RUNNING
A is running
B is running
C is running
fx.Annotate和fx.As
这两个函数通常是一块出现的,用于将一个函数的返回,强行转换成另一个接口
package main
import (
"fmt"
"go.uber.org/fx"
)
type Animal interface {
Say()
}
type A struct{}
func (receiver A) Say() {
fmt.Println("i am a")
}
func NewA() A {
fmt.Println("A is running")
return A{}
}
func Speak(animal Animal) {
animal.Say()
}
func main() {
app := fx.New(
fx.Provide(
fx.Annotate(
NewA,
fx.As(new(Animal)),
),
),
fx.Invoke(Speak),
)
app.Run()
}
// 控制台输出
[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1()
[Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm()
[Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm()
[Fx] PROVIDE main.Animal <= fx.Annotate(main.NewA(), fx.As([[main.Animal]])
[Fx] INVOKE main.Speak()
[Fx] BEFORE RUN provide: fx.Annotate(main.NewA(), fx.As([[main.Animal]])
[Fx] RUN provide: fx.Annotate(main.NewA(), fx.As([[main.Animal]]) in 10.5µs
[Fx] RUNNING
A is running
i am a
fx.ResultTags和fx.ParamTags
相信你已经发现了问题,如果你转换成了多个Animal那么fx就不知道该提供哪一个,因此需要这两个函数.
package main
import (
"fmt"
"go.uber.org/fx"
)
type Animal interface {
Say()
}
type A struct{}
func (receiver A) Say() {
fmt.Println("i am a")
}
func NewA() A {
fmt.Println("A is running")
return A{}
}
type B struct {
}
func (receiver B) Say() {
fmt.Println("i am b")
}
func NewB() B {
return B{}
}
func Speak(animal Animal) {
animal.Say()
}
func main() {
app := fx.New(
fx.Provide(
fx.Annotate(
NewA,
fx.As(new(Animal)),
),
fx.Annotate(
NewB,
fx.As(new(Animal)),
),
),
fx.Invoke(Speak),
)
app.Run()
}
// 控制台输出如下,这里"reflect".makeFuncStub 替换成"main".NewA就好理解了
cannot provide main.Animal from [0].Field0: already provided by "reflect".makeFuncStub
因此我们使用fx.ResultTags和fx.ParamTags告诉container这个函数的标记,以及我需要哪个标记的依赖.
package main
import (
"fmt"
"go.uber.org/fx"
)
type Animal interface {
Say()
}
type A struct{}
func (receiver A) Say() {
fmt.Println("i am a")
}
func NewA() A {
fmt.Println("A is running")
return A{}
}
type B struct {
}
func (receiver B) Say() {
fmt.Println("i am b")
}
func NewB() B {
return B{}
}
func Speak(animal Animal) {
animal.Say()
}
func main() {
app := fx.New(
fx.Provide(
fx.Annotate(
NewA,
fx.As(new(Animal)),
fx.ResultTags(`name:"a"`),
),
fx.Annotate(
NewB,
fx.As(new(Animal)),
fx.ResultTags(`name:"b"`),
),
),
fx.Invoke(
fx.Annotate(
Speak,
fx.ParamTags(`name:"a"`),
),
),
)
app.Run()
}
// 控制台输出如下,可以看到我们告诉fx指定Speak传递的是A
[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1()
[Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm()
[Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm()
[Fx] PROVIDE main.Animal[name = "a"] <= fx.Annotate(main.NewA(), fx.ResultTags(["name:\"a\""]), fx.As([[main.Animal]])
[Fx] PROVIDE main.Animal[name = "b"] <= fx.Annotate(main.NewB(), fx.ResultTags(["name:\"b\""]), fx.As([[main.Animal]])
[Fx] INVOKE fx.Annotate(main.Speak(), fx.ParamTags(["name:\"a\""])
[Fx] BEFORE RUN provide: fx.Annotate(main.NewA(), fx.ResultTags(["name:\"a\""]), fx.As([[main.Animal]])
[Fx] RUN provide: fx.Annotate(main.NewA(), fx.ResultTags(["name:\"a\""]), fx.As([[main.Animal]]) in 0s
[Fx] RUNNING
A is running
i am a
如果把speak注释的a改成b那么会输出i am b
group:""注释
如果你要注册多个,但是发现这样一个一个显示传参或声明太过麻烦.那么你可以直接使用group注释,然后将speak的接收参数设置为[]Animal即可
package main
import (
"fmt"
"go.uber.org/fx"
)
type Animal interface {
Say()
}
type A struct{}
func (receiver A) Say() {
fmt.Println("i am a")
}
func NewA() A {
fmt.Println("A is running")
return A{}
}
type B struct {
}
func (receiver B) Say() {
fmt.Println("i am b")
}
func NewB() B {
return B{}
}
func Speak(animals []Animal) {
for _, v := range animals {
v.Say()
}
}
func CovertToAnimalInterface(a interface{}) interface{} {
return fx.Annotate(
a,
fx.As(new(Animal)),
fx.ResultTags(`group:"animals"`),
)
}
func main() {
app := fx.New(
fx.Provide(
CovertToAnimalInterface(NewA),
CovertToAnimalInterface(NewB),
),
fx.Invoke(
fx.Annotate(
Speak,
fx.ParamTags(`group:"animals"`),
),
),
)
app.Run()
}
// 控制台输出如下
A is running
i am a
i am b
[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1()
[Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm()
[Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm()
[Fx] PROVIDE main.Animal[group = "animals"] <= fx.Annotate(main.NewA(), fx.ResultTags(["group:\"animals\""]), fx.As([[main.Animal]])
[Fx] PROVIDE main.Animal[group = "animals"] <= fx.Annotate(main.NewB(), fx.ResultTags(["group:\"animals\""]), fx.As([[main.Animal]])
[Fx] INVOKE fx.Annotate(main.Speak(), fx.ParamTags(["group:\"animals\""])
[Fx] BEFORE RUN provide: fx.Annotate(main.NewA(), fx.ResultTags(["group:\"animals\""]), fx.As([[main.Animal]])
[Fx] RUN provide: fx.Annotate(main.NewA(), fx.ResultTags(["group:\"animals\""]), fx.As([[main.Animal]]) in 0s
[Fx] BEFORE RUN provide: fx.Annotate(main.NewB(), fx.ResultTags(["group:\"animals\""]), fx.As([[main.Animal]])
[Fx] RUN provide: fx.Annotate(main.NewB(), fx.ResultTags(["group:\"animals\""]), fx.As([[main.Animal]]) in 0s
[Fx] RUNNING
fx.Supply
这个函数只能用于非接口的值.当一个构造函数不依赖任何其他的对象时,可以使用.但通常可以直接使用Provide.
fx.Provide(func() *Config { return &Config{Name: "my-app"} })
// 和下面的是一样的.
fx.Supply(&Config{Name: "my-app"})
生命周期
- 根据附加的顺序逐个启动
- 根据附加的顺序反过来逐个关闭
- fx会自动传递
fx.Lifecycle
func(context.Context) error
这里fx自动传递的context具有过期时间
package main
import (
"context"
"fmt"
"go.uber.org/fx"
)
func ExpLc1(lc fx.Lifecycle) {
lc.Append(
fx.Hook{
OnStart: func(context.Context) error {
fmt.Println("lc 1 start")
return nil
},
OnStop: func(context.Context) error {
fmt.Println("lc 1 stop")
return nil
},
},
)
}
func ExpLc2(lc fx.Lifecycle) {
lc.Append(
fx.Hook{
OnStart: func(context.Context) error {
fmt.Println("lc 2 start")
return nil
},
OnStop: func(context.Context) error {
fmt.Println("lc 2 stop")
return nil
},
},
)
}
func main() {
app := fx.New(
fx.Invoke(
ExpLc1,
ExpLc2,
),
)
app.Run()
}
// 控制台输出
[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1()
[Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm()
[Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm()
[Fx] INVOKE main.ExpLc1()
[Fx] BEFORE RUN provide: go.uber.org/fx.New.func1()
[Fx] RUN provide: go.uber.org/fx.New.func1() in 0s
[Fx] INVOKE main.ExpLc2()
[Fx] HOOK OnStart main.ExpLc1.func1() executing (caller: main.ExpLc1)
[Fx] HOOK OnStart main.ExpLc1.func1() called by main.ExpLc1 ran successfully in 0s
[Fx] HOOK OnStart main.ExpLc2.func1() executing (caller: main.ExpLc2)
[Fx] HOOK OnStart main.ExpLc2.func1() called by main.ExpLc2 ran successfully in 0s
[Fx] RUNNING
lc 1 start
lc 2 start
[Fx] INTERRUPT
[Fx] HOOK OnStop main.ExpLc2.func2() executing (caller: main.ExpLc2)
[Fx] HOOK OnStop main.ExpLc2.func2() called by main.ExpLc2 ran successfully in 0s
[Fx] HOOK OnStop main.ExpLc1.func2() executing (caller: main.ExpLc1)
[Fx] HOOK OnStop main.ExpLc1.func2() called by main.ExpLc1 ran successfully in 0s
lc 2 stop
lc 1 stop
Modules
fx.Module
第一个参数传递模块名称,用于日志中查询使用- 如果设置了
fx.Private
那么对象会被私有,任何非此module的对象将无法获取返回的对象
package main
import (
"go.uber.org/fx"
)
type A struct {
}
func NewA() A {
return A{}
}
type B struct {
A
}
func NewB(a A) B {
return B{a}
}
func main() {
app := fx.New(
fx.Module("name",
fx.Provide(
fx.Private, // 作用域仅在当前Provide
NewA,
),
),
fx.Invoke(
NewB,
),
)
app.Run()
}
// 控制台输出
[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1()
[Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm()
[Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm()
[Fx] PROVIDE (PRIVATE) main.A <= main.NewA() from module "name" // 这里能看到module的第一个参数用于命名方便日志查看
[Fx] INVOKE main.NewB()
[Fx] ERROR fx.Invoke(main.NewB()) called from:
missing type:
- main.A (did you mean to Provide it?) // module的fx.Private设置后,NewA返回的a仅在name module内能够使用,外部无法获取
参数对象和结果对象
参数对象
目的: 为函数封装依赖,提升可读性与扩展性,通常专为某个函数定义,不与其他函数共享.
type Params struct { // 命名规范: 若构造函数叫NewClient,那么参数命名为ClientParams.
// 如果构造函数叫New,参数命名为Params.
// 非强制,但官方推荐.
fx.In // 结构体必须嵌入fx.In字段
Config ClientConfig // 所有字段必须导出
HTTPClient *http.Client
Logger *zap.Logger `optional:"true"` // 可选参数,用于向后兼容,但在构造函数中必须处理nil情况
}
func New(p Params) (*Client, error) {
log := p.Logger
if log == nil {
log = zap.NewNop()
}
// 使用 log...
}
结果对象
- 命名规范同上
- 必须嵌入fx.Out
- 所有字段必须导出
- 没有 \`optional:"true"\`,如果需要可选,那么设置类型为指针,然后设置为nil
- 为某个函数定义,不与其他函数共享.
- 如果某个字段没有设置为指针类型,那么go会自动创建这个类型的结构体自动注入.