使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
Persona: 您是Go 建筑师, 用挖掘来线化应用程序图是一项面向实际任务的技能,主要用于您将容器保存在构件根, 取决于接口而非混凝土类型, 并将构造器错误作为。它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。
实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。
执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。
角色:你是一位 Go 架构师,正使用 dig 编排应用依赖图。你将容器保留在组合根(composition root),依赖接口而非具体类型,并将构造函数错误视为头等失败(first-class failure)。
基于反射的依赖注入(DI)工具包,专为支撑应用框架而设计(它是 uber-go/fx 的底层引擎),并在启动时解析对象依赖图。
官方资源:
本技能内容并非详尽无遗。请参考库的文档与代码示例获取更多信息。Context7 可作为可发现性平台提供辅助。
go get go.uber.org/dig
fx 建立在 dig 之上,共享同一容器引擎 —— DI 基元(Provide、Invoke、In/Out 结构体、命名值、值组)完全一致。fx.In/fx.Out 即是对 dig.In/dig.Out 的重新导出。
fx 在 dig 基础上额外提供的能力:
| 关注点 | dig | fx |
|---|---|---|
| DI 容器 | ✅ dig.New() |
✅(已内嵌) |
| 生命周期钩子 | ❌ | ✅ fx.Lifecycle(OnStart/OnStop) |
| 模块系统 | ❌ | ✅ fx.Module(支持作用域限定的装饰器) |
| 信号感知型运行循环 | ❌ | ✅ app.Run() 会阻塞等待 SIGINT/SIGTERM |
| 结构化事件日志 | ❌ | ✅ fx.WithLogger / fxevent |
| 启动/关闭超时 | ❌ | ✅ fx.StartTimeout / fx.StopTimeout |
选择 dig 当你仅需依赖图编排能力时:命令行工具、向调用方暴露容器的库、测试桩(test harnesses),或在已有自行管理生命周期的应用中嵌入 DI。
选择 fx 用于长期运行的服务(如 HTTP 服务器、Worker、守护进程)—— 此类场景下生命周期管理与信号处理是不可或缺的。详见 samber/cc-skills-golang@golang-uber-fx 技能。
import "go.uber.org/dig" c := dig.New()
常用选项:dig.DeferAcyclicVerification()(加快启动速度)、dig.RecoverFromPanics()(将 panic 转换为 dig.PanicError)、dig.DryRun(true)(仅校验不执行构造函数)。
// 注册一个构造函数 — 懒加载,仅在其输出被需要时才执行
err := c.Provide(func(cfg *Config) (*sql.DB, error) {
return sql.Open("postgres", cfg.DSN)
})
// 通过将其作为函数参数声明,从容器中提取服务
err = c.Invoke(func(db *sql.DB) error {
return db.Ping()
})
构造函数是懒加载且记忆化的:每种输出类型仅构建一次,并在容器内共享(即单例)。若构造函数签名非法,Provide 会在注册时立即报错;Invoke 则返回构造函数自身的错误,并附带触发该错误的完整依赖路径。
dig 构造函数可以是任意函数。输入参数代表依赖项,返回值代表被提供的类型。error(最后一个返回值)表示构造失败。遵循“接受接口,返回结构体”原则。
dig.In 的参数对象当构造函数依赖项达到 4 个及以上时,建议嵌入 dig.In,将依赖项组织为结构体字段并添加结构体标签:
type HandlerParams struct {
dig.In
Logger *zap.Logger
DB *sql.DB
Cache *redis.Client `optional:"true"` // 若未提供则使用零值
DBRO *sql.DB `name:"readonly"` // 命名依赖项
Routes []http.Handler `group:"routes"` // 值组
}
func NewHandler(p HandlerParams) *Handler { /* ... */ }
支持的标签:name:"..."、optional:"true"、group:"..."。
dig.Out 的结果对象从单个构造函数返回多个值,并为各返回值附加 name/group 标签:
type ConnResult struct {
dig.Out
ReadWrite *sql.DB `name:"primary"`
ReadOnly *sql.DB `name:"readonly"`
}
func NewConnections(cfg *Config) (ConnResult, error) { /* ... */ }
同类型存在两个提供者时会发生冲突。使用 dig.Name 显式区分:
c.Provide(NewPrimaryDB, dig.Name("primary"))
c.Provide(NewReadOnlyDB, dig.Name("readonly"))
消费时,在 dig.In 字段上添加 name:"primary" 或 name:"readonly" 标签即可。
多个提供者,一个消费者接收切片 —— 典型适用于 HTTP 处理器、健康检查、数据库迁移等场景:
type RouteResult struct {
dig.Out
Handler http.Handler `group:"routes"`
}
func NewUserHandler(db *sql.DB) RouteResult { /* ... */ }
func NewPostHandler(db *sql.DB) RouteResult { /* ... */ }
type ServerParams struct {
dig.In
Routes []http.Handler `group:"routes"`
}
Flatten(展平) —— 在标签末尾追加 ,flatten(例如 group:"routes,flatten"),可将切片解包而非嵌套。值组中元素的顺序不保证;若顺序敏感(如中间件链、迁移序列),应由单个构造函数显式提供有序切片。
dig.As)注册一个具体类型的构造函数,并直接将其暴露为一个或多个接口,无需额外编写适配器:
c.Provide(NewPostgresDB, dig.As(new(Database), new(io.Closer))) // 消费者可按需请求 Database 或 io.Closer;*PostgresDB 实现细节对外隐藏。
func main() {
c := dig.New()
must(c.Provide(NewConfig))
must(c.Provide(NewLogger))
must(c.Provide(NewDatabase))
must(c.Provide(NewServer))
err := c.Invoke(func(srv *http.Server) error {
return srv.ListenAndServe()
})
if err != nil {
log.Fatal(err)
}
}
func must(err error) { if err != nil { panic(err) } }
dig 不内置生命周期管理。若需 OnStart/OnStop 钩子、信号处理及优雅关闭,请使用 fx —— 参见 samber/cc-skills-golang@golang-uber-fx 技能。
关于 Decorate、作用域(Scopes)、可选依赖、错误辅助工具以及可视化(Visualize),请参阅 advanced.md。
*dig.Container 作为参数传递;应视其为 main() 函数内部的“管道细节”。服务定位器(Service Locator)模式会削弱 DI 带来的可测试性优势。dig.As 从宽泛结构体中暴露窄接口。dig.In 结构体)—— 调用点保持清晰易读,新增依赖只需一行字段定义,无需破坏函数签名。c.Provide 注册本模块所需类型。这使审查与重构成为模块级职责,未来也可轻松将模块迁移到 fx.Module 而无需重写依赖配置逻辑。c.Invoke,确保缺失提供者问题在启动阶段即暴露,而非首次请求时才浮现。DryRun(true) 可跳过构造函数实际执行,仅做静态校验。| 错误 | 修正方式 |
|---|---|
| 将容器传入服务中 | 容器属于 main()。应向服务注入其所需的具类型依赖;否则测试将被迫构造真实容器。 |
相同类型存在两个提供者但未使用 Name |
dig 会在 Provide 时直接报错。解决方案:为其分别命名,或合并为单个提供者,返回含 dig.Out 的结果结构体。 |
忽略 Provide 返回的错误 |
为每个 Provide 调用包裹 must 辅助函数。静默的注册失败会导致后续出现难以定位的“类型缺失”错误。 |
| 在依赖顺序敏感的场景中使用值组 | 值组本身无序。若顺序关键(如中间件链、迁移序列),应由单一构造函数显式返回有序切片。 |
| 构造函数在 import 阶段产生副作用 | 保持 init() 函数为空 —— 所有工作应在构造函数内部执行,且必须在依赖图构建完成之后启动。 |
dig 容器开销极低 —— 推荐为每个测试构建全新容器,通过 Decorate 替换特定提供者,并调用 Invoke 驱动整个系统。关于完整测试模式(按测试粒度配置依赖、共享辅助函数、CI 中依赖图校验、断言依赖注入阶段错误、恢复构造函数 panic),请参阅 testing.md。
advanced.md —— Decorate、作用域(Scopes)、可选依赖、错误辅助工具、可视化(Visualize)、完整速查表(Quick Reference)recipes.md —— 端到端示例:带路由组的 HTTP 服务器、双数据库配置、请求作用域、装饰器、dry-run 校验testing.md —— 测试模式与依赖图校验方法samber/cc-skills-golang@golang-uber-fx 技能,了解基于 dig 构建的应用生命周期管理、模块系统与信号感知型 Run()samber/cc-skills-golang@golang-dependency-injection 技能,了解 DI 核心概念与主流库横向对比samber/cc-skills-golang@golang-samber-do 技能,了解一种基于泛型、无需反射的 DI 替代方案samber/cc-skills-golang@golang-google-wire 技能,了解编译期 DI(无运行时容器)samber/cc-skills-golang@golang-structs-interfaces 技能,了解接口设计模式samber/cc-skills-golang@golang-testing 技能,了解通用测试模式若你在使用 uber-go/dig 时遇到 Bug 或非预期行为,请在 https://github.com/uber-go/dig/issues 提交 Issue。
相关专题
热门下载
相关下载
精品课程
共0课时 | 0人学习
共0课时 | 0人学习
共0课时 | 0人学习
最新文章