3. Wire 核心概念:Provider、Injector、Set、Cleanup、Error
在深入 Wire 的代码生成机制之前,必须透彻理解其五个核心抽象。它们构成了 Wire 依赖注入的全部操作单元。下面逐一拆解每个概念的定义、职责与使用场景。
3.1 Provider(提供者)—— 依赖的生产者
Provider 是 Wire 中最基本的构建块。任何能够产生一个值的函数都可以作为 Provider。Wire 通过分析 Provider 的签名来推导依赖关系。
| 特性 | 说明 |
|---|---|
| 本质 | 一个普通的 Go 函数 |
| 返回值 | 至少返回一个具体类型或接口 |
| 参数 | 表示该 Provider 的依赖项 |
| 注册方式 | 在 wire.go 中通过 wire.NewSet 或 wire.Build 引用 |
// Provider 示例:创建一个 UserService
func NewUserService(repo UserRepository, logger *Logger) *UserService {
return &UserService{
repo: repo,
logger: logger,
}
}
关键约束:Provider 的返回值类型必须唯一。如果两个 Provider 返回相同类型,Wire 会报错。此时需要使用
wire.Value 或具名 Provider 来区分。
3.2 Injector(注入器)—— 依赖的组装者
Injector 是一个由 Wire 自动生成的函数。开发者只需在 wire.go 中声明 Injector 的签名,Wire 就会根据 Provider 图生成完整的初始化代码。
// wire.go:声明 Injector
//go:build wireinject
// +build wireinject
package main
import "github.com/google/wire"
func InitializeApp() (*App, error) {
wire.Build(
NewConfig,
NewDatabase,
NewUserRepository,
NewUserService,
NewLogger,
NewApp,
)
return nil, nil // 占位返回值,Wire 会覆盖
}
执行 wire 命令后,Wire 会生成 wire_gen.go 文件,其中包含完整的初始化逻辑:
// wire_gen.go:Wire 自动生成
// Code generated by Wire. DO NOT EDIT.
func InitializeApp() (*App, error) {
config := NewConfig()
database := NewDatabase(config)
logger := NewLogger()
userRepository := NewUserRepository(database)
userService := NewUserService(userRepository, logger)
app := NewApp(userService, logger)
return app, nil
}
最佳实践:将
wire.go 放在包根目录,并使用 build tag wireinject 隔离。生成的 wire_gen.go 不应手动修改。
3.3 Set(集合)—— 逻辑分组
当项目规模增长,Provider 数量增多时,使用 wire.NewSet 可以将相关 Provider 打包成一个逻辑单元。Set 支持嵌套,便于模块化组织。
// 定义两个 Set
var DatabaseSet = wire.NewSet(
NewConfig,
NewDatabase,
)
var ServiceSet = wire.NewSet(
NewUserRepository,
NewUserService,
NewLogger,
)
// 在 Injector 中引用 Set
func InitializeApp() (*App, error) {
wire.Build(
DatabaseSet,
ServiceSet,
NewApp,
)
return nil, nil
}
| Set 特性 | 说明 |
|---|---|
| 可组合 | Set 可以包含其他 Set,形成层级结构 |
| 可复用 | 同一 Set 可在多个 Injector 中引用 |
| 绑定接口 | 通过 wire.Bind 将接口与实现绑定在 Set 内 |
| 值绑定 | 通过 wire.Value 注入常量或预构建对象 |
// 接口绑定示例
var ServiceSet = wire.NewSet(
NewUserService,
wire.Bind(new(UserServiceInterface), new(*UserService)),
)
3.4 Cleanup(清理函数)—— 资源释放
当 Provider 创建了需要释放的资源(如数据库连接、文件句柄、HTTP 客户端),可以返回一个清理函数。Wire 会自动在 Injector 中编排清理顺序,确保资源正确释放。
// Provider 返回清理函数
func NewDatabase(cfg *Config) (*Database, func(), error) {
db, err := sql.Open("postgres", cfg.DSN)
if err != nil {
return nil, nil, err
}
cleanup := func() {
db.Close()
}
return &Database{db: db}, cleanup, nil
}
Wire 生成的 Injector 会按依赖创建顺序的逆序调用清理函数:
// 生成的代码片段
func InitializeApp() (*App, func(), error) {
config := NewConfig()
database, cleanupDatabase := NewDatabase(config)
// ... 其他初始化 ...
app := NewApp(userService, logger)
return app, func() {
cleanupDatabase()
// 其他清理按逆序执行
}, nil
}
注意:清理函数必须是非阻塞的。如果清理操作可能耗时较长(如等待连接池关闭),应在清理函数内部使用超时机制或异步处理。
3.5 Error(错误处理)—— 初始化失败
Provider 可以返回 error 作为最后一个返回值。Wire 会将这些错误传播到 Injector 的返回值中,并在依赖图中短路后续初始化。
// 带错误的 Provider
func NewConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
// 解析配置...
return &Config{...}, nil
}
当某个 Provider 返回错误时,Wire 生成的代码会立即返回,并调用已创建资源的清理函数:
// 生成的错误处理逻辑
func InitializeApp() (*App, func(), error) {
config, err := NewConfig("app.yaml")
if err != nil {
return nil, nil, err
}
database, cleanup, err := NewDatabase(config)
if err != nil {
cleanup() // 清理已创建的资源
return nil, nil, err
}
// ... 后续初始化 ...
}
3.6 核心概念关系图
下面这张 SVG 图展示了五个核心概念之间的协作关系:
3.7 概念对比速查表
| 概念 | 角色 | 是否必须 | 典型返回值 |
|---|---|---|---|
| Provider | 生产依赖 | 是 | T 或 (T, error) 或 (T, func(), error) |
| Injector | 组装依赖 | 是 | (T, error) 或 (T, func(), error) |
| Set | 分组管理 | 推荐 | wire.ProviderSet |
| Cleanup | 资源释放 | 按需 | func() |
| Error | 初始化失败 | 按需 | error |
3.8 常见陷阱与避坑指南
- Provider 返回值冲突:同一类型不能有两个 Provider。使用
wire.Value或拆分为不同包解决。 - Cleanup 被忽略:如果 Injector 签名没有包含
func()返回值,Wire 会丢弃所有清理函数。 - Error 未传播:Injector 必须声明
error返回值,否则带错误的 Provider 无法使用。 - Set 循环引用:Set 之间不能形成循环依赖。Wire 会在生成时报错。
- 接口绑定遗漏:使用接口注入时,必须在 Set 中显式调用
wire.Bind。
实战建议:在项目初期就建立
internal/wire 目录,按模块划分 Set 文件(如 database_set.go、service_set.go)。每个 Set 文件只包含一个 wire.NewSet 调用和相关的 Provider 函数。这种结构在项目膨胀到 50+ Provider 时依然清晰可维护。