写点什么

fx 框架上手 - 进阶篇

作者:FunTester
  • 2024-08-01
    河北
  • 本文字数:5362 字

    阅读完需:约 18 分钟

在上一篇文章中,我们介绍了 fx 框架的基本用法,并展示了如何使用 fx 构建一个简单的服务。相信大家现在已经掌握了使用 fx 创建和管理依赖注入的基本方法以及启动应用程序的方法。为了让你的项目更加专业和高效,我们接下来将深入探讨 fx 框架的高级功能和使用技巧,如如何利用 fx.Lifecycle 管理服务生命周期,在应用启动和停止时执行特定逻辑,以及如何使用 fx.Invoke 注册启动时需要调用的函数。通过了解这些高级功能,你将能够充分发挥 fx 的潜力,构建出更加复杂和健壮的 Go 应用程序。


让我们一起探索 fx 的高级用法,提升你的编程技巧和项目质量。

fx.Invoke

上一篇文章,我们讲到 fx.Invoke 方法可以注册 fx.Lifecycle 中的 hook ,用来进行生命周期的管理。接下来我们介绍另外一个用法:用于注册需要依赖注入的函数,这些函数会在应用程序启动时调用,并且会自动接收所需的依赖项。


也就是说通过 fx.Invoke 调用一些函数在程序启动时实例化某些依赖对象。具体来说,fx.Invoke 注册的函数会在应用程序启动时被调用,这些函数的参数会自动由 fx 提供的依赖注入机制解析并注入。初始化调用有点类似 Springboot 中的 org.springframework.boot.CommandLineRunner#run ,不同的是 Springboot 会在这个方法之前实例化各个 Component 对象,而 fx 默认的是调用时候才会初始化。所以如果想在程序启动的时候初始化一些资源或者对象,就可以通过调用 fx.Invoke 方法实现。


下面是一个简单的例子:


package main    import (      "go.uber.org/fx"      "go.uber.org/zap")    func main() {      app := fx.New( //创建fx.App实例         fx.Provide(NewTester, func() *Age {            return &Age{Num: 18} //提供Age实例         }, func() *zap.Logger {            production, _ := zap.NewProduction() //提供zap.Logger实例            return production         }), //提供NewTester函数         fx.Invoke(func(*Tester) {            //调用Tester函数,默认会调用对应的 provide 方法中提供的函数,如果不需要实际调用对象,可以不写形参的名称         }),   )      app.Run() //运行fx.App实例  }    type Age struct {      Num int //年龄,整型  }    type Tester struct {      Log *zap.Logger //日志      Age *Age        //年龄  }    func NewTester(age *Age, log *zap.Logger) *Tester {      return &Tester{         Age: age,         Log: log,      }    }
复制代码


fx.Invoke 注册了一个匿名函数,该函数接收一个 Tester 类型的参数。fx 容器会确保在应用启动时,Tester 及其所有依赖(Age 和 zap.Logger)都被实例化。即使匿名函数中不使用 Tester 对象,fx 仍会调用 NewTester 以确保 Tester 被正确创建和初始化。

fx.Supply

fx.Supply 方法用于直接向 fx 框架 provide 一个对象,不用通过方法注入。主要的使用场景如下:


使用场景


  • 静态配置:你已经有了一个配置对象,可以直接将其提供给 Fx。

  • 现有实例:你有一些已经创建好的实例,可以直接注入,而不需要通过 fx.Provide。

  • 测试对象:在单元测试中,你可以使用 fx.Supply 提供一些测试对象。


下面是个简单的例子:


package main    import (      "go.uber.org/fx"      "go.uber.org/zap")    func main() {      app := fx.New( //创建fx.App实例         fx.Provide(func() *zap.Logger {            production, _ := zap.NewProduction() //提供zap.Logger实例            return production         }), //提供NewTester函数         fx.Supply(&Age{Num: 18}), //提供Age实例      )      app.Run() //运行fx.App实例  }    type Age struct {      Num int //年龄,整型  }    type Tester struct {      Log *zap.Logger //日志      Age *Age        //年龄  }    func NewTester(age *Age, log *zap.Logger) *Tester {      return &Tester{         Age: age,         Log: log,      }    }
复制代码

fx.popular

fx.PopulateFx 框架中的一个功能,用于将依赖注入到外部的变量中。它可以让你在应用启动时,将 fx 容器中的依赖直接注入到你指定的变量中,而不需要在构造函数或初始化逻辑中显式地传递这些依赖。


意思就是使用这个方法,传入一些对象的指针,然后就可以在程序启动的时候初始化创建实例了。


*需要注意的是 Populate(targets …interface{}) 中传入的 targets 必须得是目标类型 TypeX 的指针类型 TypeX,哪怕 TypeX 本身就是指针类型


下面是 fx.popular 两种使用场景:


  • 外部变量注入:需要将 fx 容器中的依赖注入到外部的全局变量或其他作用域中。

  • 测试:在单元测试中,可以方便地将依赖注入到测试用例中,便于进行依赖的替换和注入。


下面来展示一下代码:


package main    import (      "go.uber.org/fx"      "go.uber.org/zap")    var (      logger *zap.Logger      age    *Age  )    func main() {      app := fx.New(         fx.Provide(            NewLogger,            NewAge,         ),         fx.Populate(&logger, &age),         fx.Invoke(func() {            logger.Info("Application started", zap.Int("age", age.Num))         }),      )      app.Run()  }    func NewLogger() (*zap.Logger, error) {      return zap.NewProduction()  }    func NewAge() *Age {      return &Age{Num: 30}  }    type Age struct {      Num int  }
复制代码


fx.popular 方法的优势体现在下面三个方面:


  • 简化依赖注入:fx.Populate 提供了一种简洁的方式,将依赖注入到外部变量中,避免了在构造函数或初始化逻辑中显式地传递这些依赖。

  • 提高代码可读性:通过使用全局变量或特定作用域的变量,可以使代码更加直观和易读。

  • 测试友好:在测试环境中,可以方便地替换和注入依赖,便于进行单元测试和集成测试。

fx.Annotated

fx.AnnotatedFx 框架中的一个功能,用于向依赖注入容器提供带有特定标签的构造函数。这在处理依赖注入时非常有用,特别是当你有多个相同类型的实例,但需要将它们区分开来时。


当我们依赖的对象类型相同时候,可以用 fx.Annotated 方法进行对象的区分,比如我们同时要记录多种日志、连接多个数据库等等。不仅仅要在注入依赖对象的时候进行区分,也需要在使用的时候进行区分。


下面是个例子:


package main    import (      "go.uber.org/fx"      "go.uber.org/zap")    type Age struct {      Num int  }    type Tester struct {      Log *zap.Logger      Age *Age }    var Ages = fx.Provide(      fx.Annotated{         Name:   "old",         Target: NewAgeOld,      },      fx.Annotated{         Name:   "young",         Target: NewAgeYoung,      })    func main() {      app := fx.New(         fx.Provide(            NewLogger,            fx.Annotate(               NewTester,               fx.ParamTags(`name:"old"`),            ),         ),         Ages,         fx.Invoke(            func(t *Tester) {               t.Log.Info("sussess", zap.Int("age", t.Age.Num))            },         ),      )      app.Run()  }    func NewLogger() (*zap.Logger, error) {      return zap.NewProduction()  }    func NewTester(age *Age, log *zap.Logger) *Tester {      return &Tester{Log: log, Age: age}  }    func NewAgeYoung() *Age {      return &Age{Num: 18}  }    func NewAgeOld() *Age {      return &Age{Num: 60}  }
复制代码


fx.Annotated 还需要搭配 fx.Annotate 才能将参数传给创建的方法,制定同一类型对象的具体某个实例。fx.Annotated 的构造方法中还有一个参数 GroupName 不能被同时使用。下面是 Group 的例子。


package main    import (      "fmt"      "go.uber.org/fx")    type Age interface {      Print() //无返回值  }    type AgeOld struct {  }    type AgeYoung struct {  }    func (age *AgeOld) Print() {      fmt.Println("old")  }    func (age *AgeYoung) Print() {      fmt.Println("young")  }    type Man struct {      Ages []Age `group:"men"`  }    func NewApp(ages []Age) *Man {      return &Man{Ages: ages}  }    var Ages = fx.Provide(      fx.Annotated{         Group:  "men",         Target: NewAgeOld,      },      fx.Annotated{         Group:  "men",         Target: NewAgeYoung,      })    func main() {      app := fx.New(         Ages,         fx.Provide(            fx.Annotate(               NewApp,               fx.ParamTags(`group:"men"`),            ),         ),         fx.Invoke(func(man *Man) {            ages := man.Ages            for _, age := range ages {               age.Print()            }         }),      )      app.Run()  }  func NewAgeYoung() Age {      return &AgeYoung{}  }    func NewAgeOld() Age {      return &AgeOld{}  }
复制代码

fx.In 和 fx.Out

fx.Infx.Out 是 Uber 的 fx 依赖注入框架中的两个重要结构,用于管理复杂的依赖注入场景。一般在构建大型项目的时候,会经常用到 fx.Infx.Out ,对于提升代码整洁度和可维护性有很重要的作用,同时也能够避免依赖注入异常的发生。

fx.In

fx.In 用于聚合多个输入参数到一个结构体中。当我们使用 fx.In 结构体时,就无需在 fx.New() 方法中显示定义构造方法了。此时,只要当前结构体的依赖对象均在 fx 框架中定义,就可以直接创建当前的结构体对象。其主要特点是:


  • 允许将多个依赖组合成一个单一的参数

  • 支持可选依赖

  • 可以使用标签来指定特定的依赖


演示的代码如下:


package main    import (      "fmt"      "go.uber.org/fx")    type Name struct {      Str string  }    type Age struct {      Num int  }    type Tester struct {      fx.In      Age  *Age      Name *Name  }    func main() {      NewAge := func() *Age {         return &Age{Num: 30}      }      NewName := func() *Name {         return &Name{Str: "FunTester"}      }      app := fx.New(         fx.Provide(            NewAge,            NewName,         ),         fx.Invoke(func(t Tester) {            fmt.Println(t.Name.Str)         }),      )      app.Run()  }
复制代码

fx.Out

fx.Out 用于从一个函数返回多个值,这些值可以被注入到其他地方。当我们使用 fx.Out 定义一个结构体,那么当我们初始化这个结构体对象的时候,它所依赖的属性对象也会自动 providefx 框架当中。其主要特点是:


  • 允许一个函数提供多个依赖

  • 支持使用标签来命名或分组输出


下面是演示代码:


package main    import (      "fmt"      "go.uber.org/fx")    type Name struct {      Str string  }    type Age struct {      Num int  }    // Values 表示两个返回值:年龄和姓名  type Values struct {      fx.Out      Age  *Age      Name *Name  }    func NewValues() Values {      return Values{         Age:  &Age{Num: 30},         Name: &Name{Str: "FunTester"},      }  }    func main() {      app := fx.New(         // 提供构造函数         fx.Provide(            NewValues, // 使用 NewValues 而不是单独的 NewAge 和 NewName       ),         fx.Invoke(func(age *Age) {            fmt.Println(age.Num)         }),      )      app.Run()  }
复制代码

结语

到这里,常用的功能都覆盖了,当前 fx 的内容不仅仅是这些,但对于学习、开发一个 Go 项目已经足够了。相信只要不断前进,早晚会用到更高级的语法。下面我列一下我学习过程中未在文章中列举的 API

fx.module

fx.Modulefx 框架中的一个功能,用于组织和封装相关的依赖和功能。它允许开发者将一组相关的提供者(providers)和调用者(invokers)打包成一个独立的单元。这些模块可以被命名、重用和组合,从而简化大型应用的结构。fx.Module 支持嵌套和条件加载,提高了代码的模块化程度、可维护性和可测试性。它特别适用于构建复杂、可扩展的Go应用程序,使得依赖管理和功能组织变得更加清晰和高效。

fx.withlogger

fx.WithLogger 允许自定义 fx 框架的日志记录器。通过提供一个函数,该函数接收标准日志记录器并返回 fxevent.Logger,你可以替换默认的日志记录器,实现特定需求的日志记录。例如,可以集成 zap.Logger,使 fx 使用 zap 进行一致的日志记录,从而提高调试和监控的效果。

fx.As

fx.Asfx 包中的一个选项,用于将具体类型转换为其接口类型进行依赖注入。通过 fx.As,你可以在 fx.Provide 中指定将某个构造函数的返回值作为接口类型提供,使得依赖注入更加灵活和可扩展。这有助于实现松耦合和增强代码的可测试性。

发布于: 刚刚阅读数: 3
用户头像

FunTester

关注

公众号:FunTester,800篇原创,欢迎关注 2020-10-20 加入

Fun·BUG挖掘机·性能征服者·头顶锅盖·Tester

评论

发布
暂无评论
fx框架上手-进阶篇_FunTester_InfoQ写作社区