Go 的依赖注入工具分为两大阵营,dig 取其精华,去其糟粕。
12 项核心能力,覆盖 Go 依赖注入的完整场景
依赖图在 go generate 期间完成解析,错误在生成阶段即被捕获,不等到运行时。
生成的代码是纯 Go,不导入任何额外包,零反射、零开销。
仅需 Build、Provide、Supply、Invoke、Module 五个函数。
内联闭包不能捕获 InitApp 中的局部变量,由生成器强制检查。
-inline 仅将简单闭包内联为 IIFE。身份闭包无论是否传 -inline 都会塌缩为直接类型转换。
原生支持泛型函数和类型,显式实例化即可:dig.Provide(NewStore[int])
通过参数名区分同一类型的多个实例,支持多 DB 连接、多 Redis 客户端等场景。
调试日志运行时可覆盖 Logf,-debug 打印导入别名映射。
所有错误消息包含源码位置 file:line:col 与 💡 Fix: 修复建议。
层次化组合模块,内置重复检测,复用性更强。
三种模式:error(默认)、ignore、drop,灵活控制。
变量名遮蔽保护机制,自动检测并避免生成代码中的命名冲突。
三步上手 dig
go get github.com/shanjunmei/dig@latest
go install github.com/shanjunmei/dig/cmd/digen@latest
di.go//go:build digen
package main
import (
"context"
"github.com/shanjunmei/dig"
)
//go:generate go run -mod=mod github.com/shanjunmei/dig/cmd/digen -out dig_gen.go
func InitApp() func(context.Context) error {
return dig.Build(
dig.Provide(NewConfig),
dig.Provide(NewDB),
dig.Supply(DefaultTimeout),
dig.Provide(func(t Timeout) *Server { return NewServer(t) }),
dig.Invoke(func(srv *Server) error { return srv.Run() }),
)
}
main.gopackage main
import "context"
type Config struct{ Addr string }
func NewConfig() *Config { return &Config{Addr: ":8080"} }
type DB struct{}
func NewDB(*Config) *DB { return &DB{} }
type Timeout int
var DefaultTimeout Timeout = 5
type Server struct{}
func NewServer(Timeout) *Server { return &Server{} }
func (*Server) Run() error { return nil }
func main() {
if err := InitApp()(context.Background()); err != nil {
panic(err)
}
}
digen ./... # 或 go generate ./...
go run .
digen 在生成期强制的约束——违反时会给出清晰错误与 💡 Fix: 建议,而非晦涩的编译失败
每个包含 dig.Build(...) 调用的文件都必须带 //go:build digen 约束。digen 在生成的 dig_gen.go 上写死 //go:build !digen;若源文件不带对应标签,正常 go build 会同时编译两个文件并报 InitApp redeclared。digen 现在在生成期强制校验,直接给出清晰错误与 💡 Fix:,而非让重声明错误推迟暴露。
provider(通过 dig.Provide / dig.Supply / dig.Module 注册的构造函数)不得声明 context.Context 参数。provider 在 InitApp 内被即时(eager)解析,早于运行时 context.Context 的产生,故该参数在生成代码中必然 undefined。context 注入只对 dig.Invoke(func(ctx context.Context) { ... }) 合法。digen 在生成期拒绝 provider 侧的 context.Context 参数,并给出指向 dig.Invoke 或 dig.Supply 的 💡 Fix:。
带 //go:build digen 的 di.go 只能包含 dig.Build(...) 接线(Provide / Invoke / Supply / Module 调用)。所有被接线引用的领域类型、构造函数、包级变量必须定义在不带该构建标签的文件(例如 types.go)或导入的包里。原因:生成的 dig_gen.go 带 //go:build !digen,正常 go build(不带 digen 标签)时 di.go 被排除,其内符号对生成代码不可见,会导致晦涩的 undefined: X。digen 现在在写文件之前就做契约预检(checkContractVisibility):一旦接线引用了定义在 digen 文件中的主包符号,立即中止并给出清晰错误与 💡 Fix:,而不是事后由类型检查兜底。
生成代码后,digen 会对产出的 dig_gen.go 做一次 go/types 类型检查。由于用户源码在加载阶段已通过完整类型检查,生成文件上的类型错误只有两类:(a) 真正的内部生成器 bug;(b) 漏过契约预检的 digen 契约违规(仅发生在 IR 缓存命中、预检被跳过时)。两者都会中止写文件;契约违规给出与预检一致的 💡 Fix: 指引,内部 bug 则输出可点击的预填 GitHub issue 链接(外加可复制模板)以便上报——绝不会静默产出无法编译的文件。
五个函数,覆盖全部依赖注入场景
通过参数名区分同一类型的多个实例
// 提供者返回两个不同名称的 *sql.DB 实例
dig.Provide(func() (mainDB *sql.DB, reportDB *sql.DB, err error) {
main, err = connectMain()
if err != nil { return nil, nil, err }
report, err = connectReport()
return main, report, err
})
// 消费方通过参数名获取特定实例
dig.Invoke(func(mainDB *sql.DB) { // mainDB 自动注入 })
dig.Invoke(func(reportDB *sql.DB) { // reportDB 自动注入 })
| 参数 | 默认值 | 说明 |
|---|---|---|
-out | dig_gen.go | 输出文件名(./... 模式下忽略) |
-unused | error | 未使用提供者的处理策略:error / ignore / drop |
-debug | false | 启用调试日志(详细错误始终显示) |
-alias | full | 导入别名策略:full / short / obfuscated / numeric |
-inline | false | 仅将简单闭包内联为 IIFE;身份闭包始终塌缩为类型转换(与本 flag 无关) |
-typecheck | true | 生成后类型检查产出代码以捕获内部生成器 bug;大型 ./... 运行可关闭(-typecheck=false)以省去逐文件重载包图。 |
-cache | false | 将提取出的 IR 缓存到磁盘,未改动包命中缓存时跳过提取/类型检查。 |
-cachedir | "" | IR 缓存目录(默认:os.TempDir()/digen-ir-cache;仅 -cache 设置时生效)。 |
-version | false | 打印版本信息并退出 |
除默认生成运行(digen [packages...])外,digen 还提供用于脚手架、校验与检视的子命令。
| 命令 | 说明 |
|---|---|
digen init [path] | 生成带 dig.Build 入口的 di.go 脚手架 |
digen check [pkgs] | 校验 DI 契约,不写文件 |
digen graph [pkgs] | 以 Mermaid 打印提供者依赖图 |
digen explain <type> [pkgs] | 解释某类型/提供者的解析路径 |
digen completion <shell> | 输出 shell 补全脚本(bash/zsh/fish) |
dig vs Google Wire vs Uber Fx — 全维度对比
| 特性 | dig | Google Wire | Uber Fx |
|---|---|---|---|
| 方法 | 代码生成 | 代码生成 | 运行时反射 |
| 需要代码生成步骤 | ✅ digen | ✅ wire CLI | ❌ |
| 零反射 | ✅ | ✅ | ❌ |
| 零运行时依赖 | ✅ | ✅ | ❌(依赖 fx + dig 运行时) |
| 校验时机 | 生成期 | 生成期 | 运行时 |
| 提供者初始化 | 即时 | 即时 | 惰性 |
| 二进制体积影响 | 极小 | 极小 | 中等 |
| 特性 | dig | Google Wire | Uber Fx |
|---|---|---|---|
| 核心 API 数量 | 5 个 | 7 个 | 15+ 个 |
| 直接值注入 | ✅ 任意表达式 | ⚠️ 禁止函数调用 | ✅ 仅具体类型 |
| 内置 Invoke | ✅ | ❌ | ✅ |
| 模块嵌套 | ✅ 显式 | ⚠️ 扁平组合 | ✅ 显式带命名 |
| 相同类型多实例 | ✅ 命名参数 | ❌ 需包装类型 | ✅ 命名 + 值组 |
| 泛型支持 | ✅ 编译期 | ❌ | ⚠️ 仅已实例化 |
| 闭包捕获安全 | ✅ 生成器强制 | N/A | N/A |
| API 友好度 | Fx 风格极简 | 冗长反直觉 | Fx 风格极简 |
| 特性 | dig | Google Wire | Uber Fx |
|---|---|---|---|
| 错误传播模型 | Provider panic / Invoke 返回 | error 返回值传播 | app.Err() + 回滚 |
| 错误含源码位置 | ✅ file:line:col | ⚠️ 仅名称 | ⚠️ 运行时堆栈 |
| 可操作修复建议 | ✅ 💡 Fix: | ❌ | ❌ |
| 未使用提供者策略 | 3 种模式 | 仅硬错误 | N/A(惰性) |
| 不运行即可校验 | ✅ 生成即校验 | ✅ | ✅ ValidateApp |
| 调试日志 | ✅ 可覆盖 Logf | ❌ | ✅ fxevent |
| 特性 | dig | Google Wire | Uber Fx |
|---|---|---|---|
| App 生命周期对象 | ❌ 裸 func | ❌ 生成值 | ✅ *fx.App |
| 信号处理 | ❌ 调用方负责 | ❌ | ✅ 内置 |
| 编程式关停 | ❌ | ❌ | ✅ Shutdowner |
| 生命周期钩子 | ❌ | ❌ | ✅ OnStart/OnStop |
| 装饰器 | ❌ | ❌ | ✅ Decorate/Replace |
| 特性 | dig | Google Wire | Uber Fx |
|---|---|---|---|
| 维护状态 | ✅ 活跃 | ⚠️ 已归档 | ✅ 活跃 |
| 最新版本 | v1.0.24 | v0.7.0 (beta) | v1.24.0 |
| Go 版本要求 | 1.22+ | 标准 | 1.22+ |
| 重构友好度 | 高 | 低 | 中 |
28 个示例包 — 1 个可运行 example 二进制 + 库 fixture;gen_failures/ 下 35 个负向测试包(含 build-tag 变体)
//go:build digen 文件中的符号(契约违规)直观查看 digen 如何把你的提供者连接起来 —— 每一条依赖边都在编译期解析完成
运行 digen graph ./... 即可把完整依赖图以 Mermaid 格式打印出来 —— 复制到 mermaid.live 即可渲染。
digen graph ./...
# package github.com/you/app
flowchart TD
Config["Config"] --> App
DB["*DB"] --> Repository
Cache["Cache"] --> Repository
Repository["*Repository"] --> App
恒等闭包(直接返回 / 取地址 / 解引用 / 类型转换 / 类型断言五种)现始终塌缩为内联表达式,与 -inline 无关;-inline 现仅控制 IIFE 内联(默认关)。新增类型断言恒等闭包 func(p any) T { return p.(T) }。
新增 digen CLI 子命令(init/check/graph/explain/completion);生成期契约预检确保 di.go 只放接线,一旦引用 //go:build digen 文件内定义的符号即中止生成并给出 💡 Fix:;生成后类型检查安全网区分契约违规与内部生成器 bug;构建约束校验收敛到 internal/buildconstraint 单一真源;新增 golden 文件逐字节回归测试,覆盖 11 个复杂 example。
provider 不可声明 context.Context 参数(context 注入仅限 Invoke);DI 规格文件的 //go:build digen 标签现于生成期强制校验;生成后 go/types 安全网在出现内部生成器 bug 时拒绝写出坏文件并输出可点击的预填 GitHub issue 链接;gen_failures/ 新增自动化回归测试。并修复 v1.0.17 引入的回归——跨包模块内联时闭包参数/局部变量被误报为 private("var X is private"),现已放行;类型名改写由脆弱的正则改为 AST 精确改写;新增可选的稳定可序列化 IR 磁盘缓存(-cache/-cachedir,默认关)。