如何在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
相关产品推荐
相关产品推荐

