4. Google Wire 入门:安装、基本语法与 Provider / Injector

Google Wire 是 Go 生态中最成熟的编译期依赖注入工具。它通过代码生成而非运行时反射来组装依赖,既避免了 init() 地狱,又保持了 Go 静态类型检查的优势。本章将带你完成 Wire 的安装、理解核心概念,并写出第一个可运行的注入示例。

4.1 安装 Wire

Wire 是一个命令行工具,通过 go install 安装到 $GOPATH/bin:

go install github.com/google/wire/cmd/wire@latest

验证安装:

wire version
# 输出示例:wire: v0.6.0
版本兼容性提示:Wire v0.5+ 要求 Go 1.18 以上。如果你的项目仍使用 Go 1.16/1.17,请使用 wire v0.4.0。建议在 tools.go 中锁定版本,避免团队环境不一致。

4.2 核心概念:Provider 与 Injector

Wire 只有两个核心抽象——Provider(提供者)和 Injector(注入器)。理解这两个概念就能掌握 80% 的 Wire 用法。

概念 角色 表现形式 生成时机
Provider 声明“我能创建什么类型” 普通 Go 函数,返回目标类型 手写,编译前
Injector 声明“我需要什么类型,如何组装” 调用 wire.Build() 的桩函数 由 wire 命令自动生成

4.2.1 Provider:提供依赖的函数

任何返回目标类型的函数都可以作为 Provider。Wire 通过返回值类型来匹配依赖关系。

// provider.go
package main

// Provider:提供 *Config
func NewConfig() *Config {
    return &Config{Addr: ":8080"}
}

// Provider:提供 *Server,依赖 *Config
func NewServer(cfg *Config) *Server {
    return &Server{addr: cfg.Addr}
}

// Provider:提供 *App,依赖 *Server
func NewApp(srv *Server) *App {
    return &App{server: srv}
}
Provider 规则:
  • 每个 Provider 只负责创建一种类型(或一组相关类型)。
  • 参数列表是它的“依赖声明”——Wire 会自动寻找匹配的 Provider 来填充。
  • 可以返回 error 作为第二个返回值,Wire 会处理错误传播。

4.2.2 Injector:组装依赖的桩函数

Injector 是一个特殊的函数声明,它不写实现体,只调用 wire.Build() 列出所有需要的 Provider。Wire 工具会扫描这个函数并生成完整的实现。

// wire.go
//go:build wireinject
// +build wireinject

package main

import "github.com/google/wire"

// Injector 桩函数:声明最终要得到 *App
func InitializeApp() (*App, error) {
    wire.Build(NewConfig, NewServer, NewApp)
    return nil, nil  // 占位,实际由 wire 生成
}
关键约束:
  • Injector 文件必须包含 //go:build wireinject 构建标签,否则正常编译会报错(因为函数体不完整)。
  • wire.Build() 的参数是 Provider 函数,不是类型。Wire 通过返回值类型推断依赖图。
  • 桩函数的返回值类型必须与 wire.Build() 最终产出的类型一致。

4.3 生成注入代码

在包含 wire.go 的目录下执行:

wire

Wire 会生成 wire_gen.go 文件,内容大致如下:

// wire_gen.go
// Code generated by Wire. DO NOT EDIT.

package main

func InitializeApp() (*App, error) {
    config := NewConfig()
    server := NewServer(config)
    app := NewApp(server)
    return app, nil
}

生成的代码完全可读、可调试、可单步跟踪。这就是 Wire 的核心优势——编译期生成,运行时零开销。

4.4 Provider 与 Injector 的关系图

下图展示了 Provider 如何通过 Injector 组装成完整的依赖树:

Wire Provider → Injector 依赖组装流程 NewConfig() 返回 *Config NewServer(cfg) 返回 *Server NewApp(srv) 返回 *App *Config *Server Injector: InitializeApp() wire.Build(NewConfig, NewServer, NewApp) → 自动生成 wire_gen.go,组装完整依赖链 wire_gen.go(自动生成) func InitializeApp() (*App, error) { … }

4.5 完整可运行示例

将上述代码片段组合成一个完整的项目结构:

myapp/
├── main.go          # 调用 InitializeApp()
├── types.go         # Config, Server, App 类型定义
├── providers.go     # NewConfig, NewServer, NewApp
├── wire.go          # Injector 桩函数
└── wire_gen.go      # 由 wire 命令生成(不要手改)

types.go:

package main

type Config struct {
    Addr string
}

type Server struct {
    addr string
}

type App struct {
    server *Server
}

func (a *App) Run() error {
    println("App running on", a.server.addr)
    return nil
}

main.go:

package main

func main() {
    app, err := InitializeApp()
    if err != nil {
        panic(err)
    }
    app.Run()
}

执行 wire 生成代码后,运行 go run . 即可看到输出:

App running on :8080

4.6 常见错误与排查

错误现象 原因 解决方案
wire: no provider found for X 缺少返回类型 X 的 Provider 添加对应的 NewX 函数,或检查参数类型是否匹配
wire: multiple providers for X 有两个函数都返回类型 X 使用 wire.Value 或 wire.Bind 消除歧义
wire: injector file has no build tag wire.go 缺少 //go:build wireinject 在文件顶部添加构建标签
wire: unused provider Provider 的返回值未被任何 Injector 使用 移除未使用的 Provider,或创建新的 Injector 引用它
调试技巧:执行 wire check 可以只检查依赖图而不生成代码,快速定位 Provider 缺失或循环依赖问题。配合 wire diff 可以对比当前生成代码与预期差异。

4.7 本章小结

  • Provider 是普通的 Go 函数,通过返回值类型声明它能提供什么依赖。
  • Injector 是调用 wire.Build() 的桩函数,Wire 工具根据它生成完整的依赖组装代码。
  • 生成的 wire_gen.go 是纯 Go 代码,可读、可调试、无运行时反射开销。
  • 使用 //go:build wireinject 构建标签隔离桩文件与正常编译。