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 图展示了五个核心概念之间的协作关系:

Wire 核心概念关系图 Wire 核心概念协作关系 Provider func() T Set wire.NewSet(...) Injector wire.Build(...) Cleanup func() Error error wire_gen.go 自动生成 注册到 注入到 生成 可选返回 可选返回 核心流程: 1. 定义 Provider(可附带 Cleanup 和 Error) 2. 将 Provider 组织成 Set 3. 在 Injector 中引用 Set,Wire 自动生成 wire_gen.go

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 时依然清晰可维护。