4、第一个Wire项目:项目结构搭建、定义简单Provider、编写Wire.go文件、使用wire命令生成代码、运行验证

本章将带领你从零搭建第一个 Wire 项目,完整经历“项目结构规划 → 定义 Provider → 编写 Wire.go → 执行 wire 命令 → 运行验证”的全流程。通过这个最小闭环,你将直观理解 Wire 如何将手动依赖注入转化为代码生成。

4.1 项目结构搭建

我们采用 Go 标准模块布局,在项目根目录下创建以下目录和文件:

wire-demo/
├── cmd/
│   └── main.go          # 程序入口
├── internal/
│   ├── app/
│   │   └── app.go       # 应用结构体定义
│   ├── config/
│   │   └── config.go    # 配置结构体定义
│   └── service/
│       └── service.go   # 服务结构体定义
├── wire.go              # Wire 注入声明文件
├── wire_gen.go          # wire 命令自动生成(无需手动创建)
├── go.mod
└── go.sum
结构说明:internal/ 目录下的包是项目内部包,外部模块无法导入,适合存放业务核心逻辑。cmd/ 目录存放可执行文件入口,是 Go 项目的常见约定。

4.2 定义简单 Provider

Provider 是 Wire 的核心概念——它是一个返回值的函数,Wire 通过分析 Provider 的签名来构建依赖图。我们先定义三个简单的结构体及其 Provider 函数。

4.2.1 配置层(config)

// internal/config/config.go
package config

type Config struct {
    ServerPort string
    DBHost     string
}

func NewConfig() *Config {
    return &Config{
        ServerPort: ":8080",
        DBHost:     "localhost:3306",
    }
}

4.2.2 服务层(service)

// internal/service/service.go
package service

import "wire-demo/internal/config"

type Service struct {
    cfg *config.Config
}

func NewService(cfg *config.Config) *Service {
    return &Service{cfg: cfg}
}

func (s *Service) GetPort() string {
    return s.cfg.ServerPort
}

4.2.3 应用层(app)

// internal/app/app.go
package app

import "wire-demo/internal/service"

type App struct {
    svc *service.Service
}

func NewApp(svc *service.Service) *App {
    return &App{svc: svc}
}

func (a *App) Run() string {
    return "App running on port: " + a.svc.GetPort()
}
依赖链:Config → Service → App。每个 Provider 只依赖其直接需要的类型,Wire 会自动解析完整链条。

4.3 编写 Wire.go 文件

在项目根目录创建 wire.go,这是 Wire 的“注入声明文件”。它使用 //go:build wireinject 构建标签,告诉 Go 编译器在正常构建时忽略此文件,仅在 wire 命令执行时生效。

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

package main

import (
    "wire-demo/internal/app"
    "wire-demo/internal/config"
    "wire-demo/internal/service"
    "github.com/google/wire"
)

func InitializeApp() (*app.App, error) {
    wire.Build(
        config.NewConfig,
        service.NewService,
        app.NewApp,
    )
    return nil, nil
}
关键点:wire.Build 的参数是 Provider 函数,顺序无关紧要。函数体中的 return nil, nil 不会被实际执行,仅用于通过编译检查。Wire 会基于 Provider 签名自动生成真正的实现。

4.4 使用 wire 命令生成代码

确保已安装 Wire 工具:

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

在项目根目录执行:

wire

Wire 会扫描 wire.go,分析所有 Provider 的依赖关系,自动生成 wire_gen.go 文件:

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

//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package main

import (
    "wire-demo/internal/app"
    "wire-demo/internal/config"
    "wire-demo/internal/service"
)

func InitializeApp() (*app.App, error) {
    configConfig := config.NewConfig()
    serviceService := service.NewService(configConfig)
    appApp := app.NewApp(serviceService)
    return appApp, nil
}
生成结果解读:Wire 自动完成了 config.NewConfig() → service.NewService(configConfig) → app.NewApp(serviceService) 的调用链,并返回最终的 *app.App。这正是我们手动注入时需要写的代码,现在由 Wire 自动生成。

4.5 运行验证

编写 cmd/main.go 调用生成的 InitializeApp 函数:

// cmd/main.go
package main

import (
    "fmt"
    "log"
)

func main() {
    appInstance, err := InitializeApp()
    if err != nil {
        log.Fatalf("failed to initialize app: %v", err)
    }
    result := appInstance.Run()
    fmt.Println(result)
}

运行程序:

go run ./cmd/main.go

预期输出:

App running on port: :8080

4.6 完整流程可视化

下图展示了从 Provider 定义到 Wire 生成再到运行调用的完整数据流:

Wire 项目完整流程 config.NewConfig() service.NewService(cfg) app.NewApp(svc) wire.go wire.Build(...) ↓ wire 命令 wire_gen.go InitializeApp() 自动生成注入代码 go run main.go Provider 定义 每个函数返回一个类型 参数为依赖项 生成结果 自动完成依赖链 无需手动编写注入

4.7 关键概念对比

概念 说明 在本章中的体现
Provider 返回值的函数,Wire 通过它构建依赖图 NewConfig、NewService、NewApp
Injector 调用 Provider 并组装依赖的函数 InitializeApp(由 wire 生成)
wire.Build 声明需要哪些 Provider 来构建 Injector wire.go 中的 wire.Build(...)
wire_gen.go Wire 自动生成的注入代码文件 包含完整的 InitializeApp 实现

4.8 常见问题与避坑

  • 构建标签遗漏:wire.go 必须包含 //go:build wireinject,否则正常编译时会报重复定义错误。
  • Provider 返回值不匹配:Wire 要求 Provider 的返回值类型必须与 Injector 的返回类型或中间依赖类型完全一致(包括指针/非指针)。
  • 循环依赖:Wire 不支持循环依赖,如果 A 依赖 B,B 又依赖 A,Wire 会报错。
  • 多次执行 wire:每次修改 wire.go 或 Provider 签名后,都需要重新执行 wire 命令更新 wire_gen.go。