
golang-google-wire
热门使用 google/wire 在 Golang 中实现编译时依赖注入 — wire.NewSet、wire.Build、wire.Bind(接口→具体类型)、wire.Struct、wire.Value、wire.InterfaceValue、wire.FieldsOf、清理函数、//go:build wireinject 注入器文件以及生成的 wire_gen.go。适用于使用或采用 google/wire 时,代码库导入 `github.com/google/wire` 时,或通过 `wire.Build` 在编译时连接应用依赖图时。对于基于反射的运行时 DI,请参见 `samber/cc-skills-golang@golang-uber-dig` 技能。
使用 google/wire 在 Golang 中实现编译时依赖注入 — wire.NewSet、wire.Build、wire.Bind(接口→具体类型)、wire.Struct、wire.Value、wire.InterfaceValue、wire.FieldsOf、清理函数、//go:build wireinject 注入器文件以及生成的 wire_gen.go。适用于使用或采用 google/wire 时,代码库导入 `github.com/google/wire` 时,或通过 `wire.Build` 在编译时连接应用依赖图时。对于基于反射的运行时 DI,请参见 `samber/cc-skills-golang@golang-uber-dig` 技能。
角色: 你是一位使用 wire 进行编译时 DI 的 Go 架构师。你让编译器捕获缺失的依赖,将 wire_gen.go 视为已提交的源代码,并在每次依赖图更改后重新运行 wire ./...。
依赖:
- wire:
go install github.com/google/wire/cmd/wire@latest
在 Go 中使用 google/wire 进行编译时依赖注入
代码生成 DI 工具包。Wire 在编译时解析依赖图,并生成纯 Go 构造函数调用 — 没有运行时容器,没有反射。错误在运行 wire ./... 时出现,而不是在首次请求时。
注意:google/wire 已于 2025 年 8 月归档(功能完整;仍接受错误修复)。
官方资源: pkg.go.dev · github.com/google/wire · 用户指南 · 最佳实践
本技能并非详尽无遗。请参考库文档和代码示例以获取更多信息。Context7 可作为可发现性平台提供帮助。对于 Go 包文档、版本、符号和已知漏洞,→ 请参见 samber/cc-skills-golang@golang-pkg-go-dev 技能。
go get -tool github.com/google/wire/cmd/wire@latest
go get github.com/google/wire
wire 与运行时 DI 对比
| 关注点 | wire | dig / fx / samber/do |
|---|---|---|
| 解析方式 | 编译时(代码生成) | 运行时(反射) |
| 错误检测 | wire ./... 失败 |
首次 Invoke/启动时 |
| 运行时容器 | 无 — 纯 Go 调用 | 存在 |
| 生命周期钩子 | 无内置 | fx: OnStart/OnStop |
| 生成的文件 | wire_gen.go(已提交) |
无 |
有关生命周期、懒加载和完整矩阵,请参见 samber/cc-skills-golang@golang-dependency-injection。
提供者
提供者是任何 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 将提供者分组以便复用。集合可以引用其他集合。
// 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}) // 常量表达式(无函数调用/通道)
wire.InterfaceValue(new(io.Reader), os.Stdin) // 接口类型字面量
wire.FieldsOf(new(Config), "DSN", "Addr") // 将结构体字段提升为依赖图节点
有关 wire:"-" 排除标签和 wire.FieldsOf 的详细信息,请参见 advanced.md。
消除重复类型的歧义
Wire 禁止同一类型有两个提供者。将底层类型包装在不同的命名类型中,以便每个类型只有一个提供者:
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,已提交,请勿编辑)。有关包含每个包集合、清理密集型依赖图和生成输出的完整示例,请参见 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 ./...时它都会被覆盖。将其视为恰好被提交的构建产物;真相来源是提供者和注入器文件。 - 始终在注入器文件中添加
//go:build wireinject— 省略它会导致重复符号编译错误,因为存根和生成的文件定义了相同的函数。 - 使用命名类型来区分相同底层类型的值 — wire 强制每个类型只有一个提供者;命名类型如
type DSN string允许PrimaryDSN和ReplicaDSN共存。 - 保持库提供者集合最小且向后兼容 — 添加新的必需输入会破坏下游注入器;移除输出也会。仅在同一版本中引入新创建的类型。
- 从清理提供者返回
(T, func(), error)并让 wire 链接它们 — wire 生成正确的逆序清理并处理部分失败(如果构造中途失败,仅运行已构建的清理函数)。 - 保持注入器文件专注 — 每个文件一个函数,每次一个包导入。包含数十个
wire.Build参数的大型注入器难以推理;委托给每个包的集合。
常见错误
| 错误 | 修复 |
|---|---|
手动编辑 wire_gen.go |
切勿编辑。更改提供者或注入器并重新运行 wire ./...。 |
缺少 //go:build wireinject |
在每个注入器文件的第一行添加该标签。 |
两个提供者返回 *sql.DB |
使用命名结构体类型包装:type PrimaryDB struct { *sql.DB } — Wire 不区分指针类型别名。 |
注入接口时未使用 wire.Bind |
在提供者集合中添加 wire.Bind(new(MyInterface), new(*MyImpl))。 |
更改后忘记重新运行 wire ./... |
在 go build 之前运行 wire;将其添加到 go generate 或 Makefile 目标中。 |
调用 cleanup() 时未检查 nil |
Wire 在构造错误时返回 nil 清理函数;使用 if cleanup != nil { defer cleanup() } 保护。 |
测试
Wire 生成纯 Go 构造函数,因此单元测试使用手动注入 — 无需克隆或重置容器。有关测试模式(测试注入器将真实提供者替换为假提供者,CI 检查 wire_gen.go 是否过时),请参见 testing.md。
进一步阅读
- advanced.md — 清理链、多个注入器、集合嵌套、错误目录、代码生成标志、快速参考
- recipes.md — HTTP 服务器、多注入器构建、清理密集型依赖图、CLI 嵌入
- testing.md — 测试注入器、假绑定、CI 过时检查
交叉引用
- → 参见
samber/cc-skills-golang@golang-dependency-injection技能了解 DI 概念和库比较 - → 参见
samber/cc-skills-golang@golang-uber-dig技能了解基于运行时反射的 DI(无生命周期) - → 参见
samber/cc-skills-golang@golang-uber-fx技能了解带生命周期钩子、模块和信号感知 Run() 的运行时 DI - → 参见
samber/cc-skills-golang@golang-samber-do技能了解基于泛型的 DI(无反射) - → 参见
samber/cc-skills-golang@golang-structs-interfaces技能了解接口设计模式 - → 参见
samber/cc-skills-golang@golang-testing技能了解通用测试模式
如果你在 google/wire 中遇到错误或意外行为,请在 https://github.com/google/wire/issues 提交问题。





