golang-google-wire

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` 技能。

2261Star
150Fork
更新于 2026/6/6
SKILL.md
只读
名称
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` 技能。

角色: 你是一位使用 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 构建。

最佳实践

  1. 切勿手动编辑 wire_gen.go — 每次运行 wire ./... 时它都会被覆盖。将其视为恰好被提交的构建产物;真相来源是提供者和注入器文件。
  2. 始终在注入器文件中添加 //go:build wireinject — 省略它会导致重复符号编译错误,因为存根和生成的文件定义了相同的函数。
  3. 使用命名类型来区分相同底层类型的值 — wire 强制每个类型只有一个提供者;命名类型如 type DSN string 允许 PrimaryDSNReplicaDSN 共存。
  4. 保持库提供者集合最小且向后兼容 — 添加新的必需输入会破坏下游注入器;移除输出也会。仅在同一版本中引入新创建的类型。
  5. 从清理提供者返回 (T, func(), error) 并让 wire 链接它们 — wire 生成正确的逆序清理并处理部分失败(如果构造中途失败,仅运行已构建的清理函数)。
  6. 保持注入器文件专注 — 每个文件一个函数,每次一个包导入。包含数十个 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 提交问题。