在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
Persona: 您是使用线条进行编译的 Go 建筑师是一项面向实际任务的技能,主要用于您让编译器捕捉缺失的依赖性, 将线条 gen. go 当作承诺源, 并重运行线条。它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。
实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。
执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。
角色:你是一位使用 wire 实现编译期依赖注入(DI)的 Go 架构师。你依赖编译器捕获缺失依赖,将 wire_gen.go 视为已提交的源码,并在每次依赖图变更后重新运行 wire ./...。
依赖项:
go install github.com/google/wire/cmd/wire@latest一种基于代码生成的 DI 工具包。wire 在编译期解析依赖图,并生成标准 Go 构造函数调用——不依赖运行时容器,也不使用反射。错误会在你执行 wire ./... 时暴露,而非在首次请求时才出现。
注意:google/wire 已于 2025 年 8 月归档(功能已完成;仍接受 Bug 修复)。
官方资源:pkg.go.dev · github.com/google/wire · 用户指南 · 最佳实践
本技能文档并非详尽无遗。请参考库的官方文档与代码示例获取更多信息。Context7 可作为可发现性平台提供辅助。
go get -tool github.com/google/wire/cmd/wire@latest go get github.com/google/wire
| 关注点 | wire | dig / fx / samber/do |
|---|---|---|
| 解析时机 | 编译期(代码生成) | 运行时(反射) |
| 错误检测 | wire ./... 执行失败 |
首次 Invoke/启动时 |
| 运行时容器 | 无——纯 Go 调用 | 存在 |
| 生命周期钩子 | 未内置支持 | fx: OnStart/OnStop |
| 生成文件 | wire_gen.go(需提交) |
无 |
有关生命周期管理、懒加载及完整能力矩阵,请参阅 samber/cc-skills-golang@golang-dependency-injection。
Provider 是任意 Go 函数:输入参数为依赖项,返回值为被提供的类型。支持三种返回形式:
func NewConfig() *Config { return &Config{Addr: ":8080"} }
func NewDB(cfg *Config) (*sql.DB, error) { return sql.Open("postgres", cfg.DSN) }
func NewRedis(cfg *Config) (*redis.Client, func(), error) { // 清理函数按逆序链式调用
c := redis.NewClient(&redis.Options{Addr: cfg.RedisAddr})
return c, func() { c.Close() }, nil
}
wire.NewSet 将多个 Provider 组合以供复用。集合之间可相互引用。
// infra/wire.go
var InfraSet = wire.NewSet(
NewConfig,
NewDB,
NewRedis,
)
// service/wire.go
var ServiceSet = wire.NewSet(
NewUserRepo,
NewUserService,
wire.Bind(new(UserStore), new(*UserRepo)), // 接口绑定
)
保持集合粒度细小:库级集合应暴露稳定接口(增加输入或移除输出均会破坏下游注入器)。每包一个集合是实用的默认约定。
//go:build wireinject注入器文件声明初始化函数。Wire 将其函数体生成到 wire_gen.go 中,并替换原始存根。
//go:build wireinject
package main
import "github.com/google/wire"
// Wire 生成该函数的函数体。
func InitApp() (*App, func(), error) {
wire.Build(InfraSet, ServiceSet, NewApp)
return nil, nil, nil // 由代码生成器替换
}
//go:build wireinject 构建标签防止该存根文件被编译进二进制——仅 wire_gen.go(不含此标签)能通过 go build 编译。若缺少该标签,则两个文件均定义同名函数,导致编译错误。
当存根返回值写法不便时,可采用替代语法:
func InitApp() (*App, func(), error) {
panic(wire.Build(InfraSet, ServiceSet, NewApp))
}
Wire 禁止隐式接口实现——你必须显式声明绑定,确保当多个类型实现同一接口时,依赖图依然明确无歧义。
var Set = wire.NewSet(
NewPostgresUserRepo,
wire.Bind(new(UserStore), new(*PostgresUserRepo)), // 告知 Wire:*PostgresUserRepo 满足 UserStore 接口
)
显式绑定可避免因其他位置新增同接口实现类型而导致依赖图意外中断。
wire.Struct 可直接从依赖图填充结构体字段,无需手动编写构造函数。使用 wire:"-" 标签可排除特定字段。
wire.Struct(new(Server), "Logger", "DB") // 注入指定命名字段
wire.Struct(new(Server), "*") // 注入所有未被排除的字段
wire.Value(Foo{X: 42}) // 常量表达式(禁止函数调用 / channel 操作)
wire.InterfaceValue(new(io.Reader), os.Stdin) // 接口类型字面量
wire.FieldsOf(new(Config), "DSN", "Addr") // 将结构体字段提升为依赖图节点
关于 wire:"-" 排除标签与 wire.FieldsOf 的详细说明,请参阅 advanced.md。
Wire 禁止为同一类型注册两个 Provider。可通过为底层类型定义不同的具名类型,使每个类型恰好对应一个 Provider:
type PrimaryDSN string type ReplicaDSN string
// wire.go — 注入器文件,通过构建标签排除在二进制之外
//go:build wireinject
package main
func InitApp() (*App, func(), error) {
wire.Build(config.ConfigSet, infra.InfraSet, service.ServiceSet, NewApp)
return nil, nil, nil
}
// main.go
func main() {
app, cleanup, err := InitApp()
if err != nil { log.Fatal(err) }
defer cleanup()
app.Run()
}
Wire 会生成 wire_gen.go(纯 Go 代码,需提交,切勿手动编辑)。含分包 Provider Set、复杂清理逻辑及生成结果的完整示例,请参阅 recipes.md。
wire ./... # 为模块中所有注入器重新生成代码 wire check ./... # 验证依赖图但不生成代码(适用于快速 CI 检查)
每次构造函数签名变更后都需运行 wire ./...。可在注入器文件顶部添加 //go:generate go run github.com/google/wire/cmd/wire,以便 go generate ./... 同样生效。务必提交 wire_gen.go——它必须与源码保持同步,以保障 CI 构建成功。
wire_gen.go——该文件会在每次 wire ./... 运行时被完全覆盖。应将其视为已提交的构建产物;真实可信来源是 Provider 和注入器文件。//go:build wireinject 标签——遗漏会导致符号重复的编译错误(存根与生成文件同时定义了相同函数)。type DSN string 可让你共存 PrimaryDSN 与 ReplicaDSN。(T, func(), error),交由 Wire 自动链式调用——Wire 会生成正确逆序的清理逻辑,并妥善处理部分失败场景(若构造中途失败,仅已成功构造对象的清理函数会被执行)。wire.Build 参数的臃肿注入器难以理解;应委托给各包专属的 Provider Set。| 错误 | 修正方式 |
|---|---|
手动编辑 wire_gen.go |
切勿编辑。修改 Provider 或注入器后,重新运行 wire ./...。 |
遗漏 //go:build wireinject |
在每个注入器文件最开头添加该标签。 |
两个 Provider 均返回 *sql.DB |
使用具名结构体包装:type PrimaryDB struct { *sql.DB }——Wire 不区分指针类型别名。 |
注入接口但未使用 wire.Bind |
在 Provider Set 中添加 wire.Bind(new(MyInterface), new(*MyImpl))。 |
变更后忘记重新运行 wire ./... |
在 go build 前运行 wire;可将其加入 go generate 或 Makefile 目标。 |
调用 cleanup() 时未判断 nil |
Wire 在构造出错时返回 nil 清理函数;应使用 if cleanup != nil { defer cleanup() } 加以防护。 |
Wire 生成的是标准 Go 构造函数,因此单元测试采用手动注入——无需克隆或重置容器。关于测试模式(如用 fake Provider 替换真实 Provider 的测试注入器、CI 中检查 wire_gen.go 是否过期),请参阅 testing.md。
samber/cc-skills-golang@golang-dependency-injection 技能,了解 DI 概念与各库对比samber/cc-skills-golang@golang-uber-dig 技能,了解无生命周期管理的运行时反射型 DIsamber/cc-skills-golang@golang-uber-fx 技能,了解支持生命周期钩子、模块化及信号感知 Run() 的运行时 DIsamber/cc-skills-golang@golang-samber-do 技能,了解基于泛型、无需反射的 DIsamber/cc-skills-golang@golang-structs-interfaces 技能,了解接口设计模式samber/cc-skills-golang@golang-testing 技能,了解通用测试模式如在使用 google/wire 时遇到 Bug 或异常行为,请在 https://github.com/google/wire/issues 提交 Issue。
相关专题
热门下载
相关下载
精品课程
共0课时 | 0人学习
共0课时 | 0人学习
共0课时 | 0人学习