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 生成再到运行调用的完整数据流:
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。