前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >fx框架上手-进阶篇

fx框架上手-进阶篇

作者头像
FunTester
发布2024-08-01 09:17:59
680
发布2024-08-01 09:17:59
举报
文章被收录于专栏:FunTester

在上一篇文章中,我们介绍了 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 方法实现。

下面是一个简单的例子:

代码语言:javascript
复制
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 提供一些测试对象。

下面是个简单的例子:

代码语言:javascript
复制
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 容器中的依赖注入到外部的全局变量或其他作用域中。
  • 测试:在单元测试中,可以方便地将依赖注入到测试用例中,便于进行依赖的替换和注入。

下面来展示一下代码:

代码语言:javascript
复制
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 方法进行对象的区分,比如我们同时要记录多种日志、连接多个数据库等等。不仅仅要在注入依赖对象的时候进行区分,也需要在使用的时候进行区分。

下面是个例子:

代码语言:javascript
复制
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 的例子。

代码语言:javascript
复制
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 框架中定义,就可以直接创建当前的结构体对象。其主要特点是:

  • 允许将多个依赖组合成一个单一的参数
  • 支持可选依赖
  • 可以使用标签来指定特定的依赖

演示的代码如下:

代码语言:javascript
复制
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 框架当中。其主要特点是:

  • 允许一个函数提供多个依赖
  • 支持使用标签来命名或分组输出

下面是演示代码:

代码语言:javascript
复制
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 中指定将某个构造函数的返回值作为接口类型提供,使得依赖注入更加灵活和可扩展。这有助于实现松耦合和增强代码的可测试性。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-07-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FunTester 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • fx.Invoke
  • fx.Supply
  • fx.popular
  • fx.Annotated
  • fx.In 和 fx.Out
    • fx.In
      • fx.Out
      • 结语
        • fx.module
          • fx.withlogger
            • fx.As
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档