Uber Fx 依赖注入框架学习笔记
1. 什么是 Uber Fx?
uber-go/fx 是 Uber 开源的一个 Go 语言依赖注入(Dependency Injection,简称 DI)框架。它旨在简化 Go 应用程序的初始化、配置和生命周期管理。
核心功能
- 依赖注入: 自动解决组件之间的依赖关系,无需手动编写繁琐的初始化代码。
- 生命周期管理: 提供
OnStart和OnStop钩子,管理应用的启动和关闭流程。 - 模块化: 鼓励将应用拆分为可复用的模块 (
fx.Module)。
2. 为什么要用 Fx?
在没有 DI 框架时,我们的 main 函数通常是这样的(“手动挡”):
func main() {
// 手动管理依赖顺序
config := NewConfig()
db := NewDatabase(config) // db 依赖 config
logger := NewLogger(config) // logger 依赖 config
service := NewService(db, logger) // service 依赖 db 和 logger
server := NewServer(service) // server 依赖 service
server.Start()
}
痛点:
- 顺序耦合: 必须严格按照依赖顺序初始化,写错顺序会报错。
- 修改困难: 如果
NewService增加了一个参数,所有调用它的地方都要改。 - 全局状态: 容易导致滥用全局变量(
init函数)。
Fx 的优势 (“自动挡”):
- 声明式: 你只需要告诉 Fx “我有这个构造函数”,Fx 会自动计算依赖图并按需初始化。
- 解耦: 组件之间只通过接口交互,不需要知道具体的创建过程。
3. Fx 的核心概念与使用
3.1 fx.Provide: 注册提供者 (构造函数)
告诉 Fx 如何创建对象。Fx 通过函数的返回值类型来建立索引。
// 构造函数:提供 Greeter 接口
// Fx 记录:Greeter (接口) -> 由 NewGreeter 函数提供
func NewGreeter() Greeter {
return &ConsoleGreeter{Name: "Console Greeter"}
}
// 构造函数:提供 *User 实例
// Fx 记录:*User (类型) -> 由 NewUser 函数提供
// Fx 发现参数需要 Greeter,会自动去查找谁提供了 Greeter
func NewUser(g Greeter) *User {
return &User{Greeter: g}
}
// 在 main 中注册
fx.Provide(NewGreeter, NewUser)
关键点:
- 接口绑定: 如果构造函数返回接口类型(如
Greeter),Fx 就能注入给需要该接口的地方。 - 类型严格: 如果返回
*ConsoleGreeter,Fx 就只能注入给需要*ConsoleGreeter的地方,不能自动注入给Greeter接口。
3.2 fx.Invoke: 触发注入 (入口函数)
告诉 Fx 从哪里开始执行。通常用于启动服务器、注册处理器或执行一次性任务。
// 这是一个普通的函数,作为入口
// Fx 看到参数 *User,会自动创建并注入
func RegisterUser(lifecycle fx.Lifecycle, u *User) {
// ...
}
// 在 main 中调用
fx.Invoke(RegisterUser)
执行流程:
- Fx 看到
Invoke(RegisterUser)。 - 检查
RegisterUser参数 -> 需要*User。 - 查找
*User提供者 ->NewUser。 - 检查
NewUser参数 -> 需要Greeter。 - 查找
Greeter提供者 ->NewGreeter。 - 开始执行:
NewGreeter()->NewUser()->RegisterUser()。
3.3 fx.Lifecycle: 生命周期管理
允许你介入应用的启动和停止过程。
func RegisterUser(lifecycle fx.Lifecycle, u *User) {
lifecycle.Append(fx.Hook{
// OnStart: 应用启动时执行 (app.Run() 之后)
// context 包含启动超时时间
OnStart: func(ctx context.Context) error {
fmt.Println("应用启动...")
u.Greeter.SayHello()
return nil
},
// OnStop: 应用停止时执行 (Ctrl+C 或 app.Stop())
// 用于清理资源,如关闭数据库连接
OnStop: func(ctx context.Context) error {
fmt.Println("应用停止...")
return nil
},
})
}
3.4 fx.New vs app.Run
fx.New(...): 配置阶段。定义依赖图,注册构造函数和入口函数。此时不会创建实例。app.Run(): 运行阶段。解析依赖,创建实例,执行Invoke,运行OnStart,然后阻塞等待停止信号。