fx库解决go依赖注入

Fx is the backbone of nearly all Go services at Uber.

----https://uber-go.github.io/fx/index.html

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"})

生命周期

  1. 根据附加的顺序逐个启动
  2. 根据附加的顺序反过来逐个关闭
  3. fx会自动传递fx.Lifecycle
  4. 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

  1. fx.Module第一个参数传递模块名称,用于日志中查询使用
  2. 如果设置了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...
}

结果对象

  1. 命名规范同上
  2. 必须嵌入fx.Out
  3. 所有字段必须导出
  4. 没有 \`optional:"true"\`,如果需要可选,那么设置类型为指针,然后设置为nil
  5. 为某个函数定义,不与其他函数共享.
  6. 如果某个字段没有设置为指针类型,那么go会自动创建这个类型的结构体自动注入.
go

我来吐槽

*