You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Uber Fx的lifecycle中传入Foo的两种不同实现?

问题:如何向Fx的Lifecycle函数传入Foo接口的两种不同实现?

我需要让以下Go代码正常运行,核心需求是给lifecycle函数传入Foo接口的两个不同实现,但目前直接注入会报错。

原始代码

package main

import (
    "context"
    "fmt"
    "go.uber.org/fx"
)

type Foo interface {
    Print()
}

type Bar struct{}

func (b *Bar) Print() {
    fmt.Println("I'm bar")
}

type Baz struct{}

func (b *Baz) Print() {
    fmt.Println("I'm baz")
}

func foo() Foo {
    return &Bar{}
}

func anotherFoo() Foo {
    return &Baz{}
}

func main() {
    workingStart() // 这段能正常运行
    //nonWorkingStart() // 这段无法运行
}

func nonWorkingStart() {
    app := fx.New(
        fx.Provide(fx.Annotate(foo, fx.ResultTags(`name:"bar"`))),
        fx.Provide(fx.Annotate(anotherFoo, fx.ResultTags(`name:"baz"`))),
        // 如何配置让nonWorkingRun能拿到两个不同的Foo实例?
        fx.Invoke(nonWorkingRun),
    )

    if app.Err() != nil {
        panic(fmt.Errorf("unable to bootstrap app: %w", app.Err()))
    }

    app.Run()
}

func workingStart() {
    app := fx.New(
        fx.Provide(foo),
        fx.Invoke(workingRun),
    )

    if app.Err() != nil {
        panic(fmt.Errorf("unable to bootstrap app: %w", app.Err()))
    }

    app.Run()
}

func nonWorkingRun(lifecycle fx.Lifecycle, foo Foo, anotherFoo Foo) {
    lifecycle.Append(
        fx.Hook{
            OnStart: func(context.Context) error {
                foo.Print()
                anotherFoo.Print()
                return nil
            },
        },
    )
}

func workingRun(lifecycle fx.Lifecycle, foo Foo) {
    lifecycle.Append(
        fx.Hook{
            OnStart: func(context.Context) error {
                foo.Print()
                return nil
            },
        },
    )
}

我自己的实现方式

我通过结构体包装Foo切片并结合fx.ParamTags实现了功能,但不确定有没有其他更直接的方法:

type FooSlices struct {
    fooSlices []Foo
}

func fooSlices(foo, anotherFoo Foo) FooSlices {
    return FooSlices{fooSlices: []Foo{foo, anotherFoo}}
}

func main() {
    //workingStart()
    nonWorkingStart()
}

func nonWorkingStart() {
    app := fx.New(
        fx.Provide(fx.Annotate(foo, fx.ResultTags(`name:"bar"`))),
        fx.Provide(fx.Annotate(anotherFoo, fx.ResultTags(`name:"baz"`))),
        fx.Provide(fx.Annotate(fooSlices, fx.ParamTags(`name:"bar"`, `name:"baz"`))),
        fx.Invoke(nonWorkingRun),
    )

    if app.Err() != nil {
        panic(fmt.Errorf("unable to bootstrap app: %w", app.Err()))
    }

    app.Run()
}

func nonWorkingRun(lifecycle fx.Lifecycle, fooSlices FooSlices) {
    lifecycle.Append(
        fx.Hook{
            OnStart: func(context.Context) error {
                for _, foo := range fooSlices.fooSlices {
                    foo.Print()
                }
                return nil
            },
        },
    )
}

其他可行的实现方法

方法1:直接给Invoke函数的参数添加名称标签

不需要额外结构体,直接给nonWorkingRun的参数标注对应的名称标签,Fx就能精准注入指定实例:

func nonWorkingStart() {
    app := fx.New(
        fx.Provide(fx.Annotate(foo, fx.ResultTags(`name:"bar"`))),
        fx.Provide(fx.Annotate(anotherFoo, fx.ResultTags(`name:"baz"`))),
        // 给Invoke的函数添加参数标签,第一个空串对应lifecycle参数(无需标签)
        fx.Invoke(fx.Annotate(nonWorkingRun, fx.ParamTags(``, `name:"bar"`, `name:"baz"`))),
    )

    if app.Err() != nil {
        panic(fmt.Errorf("unable to bootstrap app: %w", app.Err()))
    }

    app.Run()
}

func nonWorkingRun(lifecycle fx.Lifecycle, foo Foo, anotherFoo Foo) {
    lifecycle.Append(
        fx.Hook{
            OnStart: func(context.Context) error {
                foo.Print()
                anotherFoo.Print()
                return nil
            },
        },
    )
}

方法2:使用Fx的组(Group)自动收集所有Foo实现

如果后续可能新增更多Foo实现,用组功能更灵活,Fx会自动把同组的实例收集成切片:

func foo() Foo {
    return &Bar{}
}

func anotherFoo() Foo {
    return &Baz{}
}

// 把所有Foo实现加入到"foos"组
func provideFooGroup() fx.Option {
    return fx.Provide(
        fx.Annotate(foo, fx.ResultTags(`group:"foos"`)),
        fx.Annotate(anotherFoo, fx.ResultTags(`group:"foos"`)),
    )
}

func nonWorkingStart() {
    app := fx.New(
        provideFooGroup(),
        fx.Invoke(nonWorkingRun),
    )

    if app.Err() != nil {
        panic(fmt.Errorf("unable to bootstrap app: %w", app.Err()))
    }

    app.Run()
}

// 直接注入Foo切片,自动获取组内所有实例
func nonWorkingRun(lifecycle fx.Lifecycle, foos []Foo) {
    lifecycle.Append(
        fx.Hook{
            OnStart: func(context.Context) error {
                for _, f := range foos {
                    f.Print()
                }
                return nil
            },
        },
    )
}

总结

除了结构体包装的方式,还有两种更直接的方案:

  • 给Invoke函数的参数添加名称标签,适合需要精准指定实例的场景
  • 使用Fx的组功能自动收集同接口实现,适合批量处理或后续可能扩展的场景

内容的提问来源于stack exchange,提问作者serverfaces

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 13:25:38