关于 golang-google-wire
面向 Go 开发者的 google/wire 使用指南,用于构建编译期依赖图并生成普通 Go 调用,在运行前发现缺失依赖。内容涵盖 Provider、Injector、清理函数和代码生成实践。
主要功能
- 01编译期解析依赖
- 02生成普通 Go 构造调用
- 03复用 Provider 集
- 04显式绑定接口
- 05串联清理函数
- 06生成代码并检查依赖图
适用场景
- 01为 Go 服务装配依赖
- 02在构建前验证依赖图
- 03依赖图变更后生成代码
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-google-wire安装后在 Claude Code 中输入/golang-google-wire确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/advanced.mdrecipes.mdtesting.md详细介绍
**角色设定:**你是一位使用 wire 进行编译期 DI 的 Go 架构师。你让编译器检测缺失的依赖,将 wire_gen.go 视为已提交的源文件,并在每次依赖图变更后重新运行 wire ./...。
依赖项:
- wire:
go install github.com/google/wire/cmd/wire@latest
使用 google/wire 在 Go 中进行编译期依赖注入
代码生成式 DI 工具包。Wire 在编译期解析依赖图并生成纯 Go 构造函数调用——没有运行时容器,也不使用反射。运行 wire ./... 时就会发现错误,而不是等到首次请求时才发现。
注意:google/wire 于 2025 年 8 月归档(功能已完备;仍接受错误修复)。
官方资源:pkg.go.dev · github.com/google/wire · 用户指南 · 最佳实践
本技能并未涵盖所有内容——更多信息请参阅库文档和代码示例:
- 如需了解 Go 包文档、符号、版本、导入者和已知漏洞,→ 请参阅
samber/cc-skills-golang@golang-pkg-go-dev技能(godig);查询 Go 包信息时优先于 Context7。 - 如需在自己的代码中导航此库的用法(定义、调用位置、诊断信息),→ 请参阅
samber/cc-skills-golang@golang-gopls技能(gopls)。 - 对于 pkg.go.dev 未收录的文档,Context7 仍可作为备用方案。
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。
Provider
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) { // cleanup chained in reverse order
c := redis.NewClient(&redis.Options{Addr: cfg.RedisAddr})
return c, func() { c.Close() }, nil
}
Provider 集
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)), // interface binding
)
保持集合精简:库集合应提供稳定的接口(添加输入或移除输出会破坏下游注入器)。每个包一个集合是一个实用的默认做法。
注入器与 //go:build wireinject
注入器文件声明初始化函数。Wire 会将函数体生成到 wire_gen.go 中,并替换存根。
//go:build wireinject
package main
import "github.com/google/wire"
// Wire generates the body of this function.
func InitApp() (*App, func(), error) {
wire.Build(InfraSet, ServiceSet, NewApp)
return nil, nil, nil // replaced by codegen
}
//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)), // tell wire: *PostgresUserRepo satisfies UserStore
)
显式绑定可避免在其他位置添加新的同接口实现类型时导致依赖图出错。
结构体 Provider 和值
wire.Struct 可以从依赖图中填充结构体字段,无需手动编写构造函数。为要排除的字段添加 wire:"-" 标签。
wire.Struct(new(Server), "Logger", "DB") // inject named fields
wire.Struct(new(Server), "*") // inject all non-excluded fields
wire.Value(Foo{X: 42}) // constant expression (no fn calls / channels)
wire.InterfaceValue(new(io.Reader), os.Stdin) // interface-typed literal
wire.FieldsOf(new(Config), "DSN", "Addr") // promote struct fields as graph nodes
有关 wire:"-" 排除标签和 wire.FieldsOf 的详细信息,请参阅 advanced.md。
区分重复类型
Wire 禁止为同一类型定义两个 Provider。将底层类型包装为不同的命名类型,使每种类型恰好有一个 Provider:
type PrimaryDSN string
type ReplicaDSN string
完整应用示例
// wire.go — injector, excluded from binary via build tag
//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 ./... # regenerate all injectors in the module
wire check ./... # validate graph without regenerating (fast CI check)
每次更改构造函数签名后,都要运行 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 过期检查
交叉引用
- → DI 概念和库比较请参阅
samber/cc-skills-golang@golang-dependency-injection技能 - → 无生命周期的运行时反射式 DI 请参阅
samber/cc-skills-golang@golang-uber-dig技能 - → 带生命周期钩子、模块和信号感知型 Run() 的运行时 DI 请参阅
samber/cc-skills-golang@golang-uber-fx技能 - → 基于泛型且无需反射的 DI 请参阅
samber/cc-skills-golang@golang-samber-do技能 - → 接口设计模式请参阅
samber/cc-skills-golang@golang-structs-interfaces技能 - → 通用测试模式请参阅
samber/cc-skills-golang@golang-testing技能
如果你在 google/wire 中遇到错误或异常行为,请在 https://github.com/google/wire/issues 提交 issue。