依赖注入

依赖注入是把对象依赖的组件从外部传入,而不是在对象内部直接创建。Go 通常使用构造函数显式注入;只有依赖图变得复杂时,才需要引入工具。

方式注入时机特点适用场景
手动组装编译期无额外依赖,调用关系最直观小型和中型项目
Wire代码生成生成普通 Go 代码,不使用反射已使用 Wire 的存量项目
Dig运行时基于反射构建依赖容器需要动态组装依赖的库或框架
Fx运行时基于 Dig,并管理应用生命周期和模块组件较多的长期运行服务

新项目应先考虑手动组装。Wire 已于 2025 年 8 月归档且不再维护,不建议在新项目中引入;需要容器时,通常直接选择 Fx,而不是在业务代码中使用 Dig。

下面的示例使用相同的构造函数:

type Repository struct {
    db *sql.DB
}

func NewRepository(db *sql.DB) *Repository {
    return &Repository{db: db}
}

type Service struct {
    repo *Repository
}

func NewService(repo *Repository) *Service {
    return &Service{repo: repo}
}

手动组装

在程序入口按依赖顺序调用构造函数,依赖关系可以由编译器检查。

func main() {
    db, err := NewDB()
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    repo := NewRepository(db)
    service := NewService(repo)

    run(service)
}

当初始化代码仍然清晰时,手动组装通常是最简单的选择。

Wire

Wire 根据 Provider 的参数和返回值生成组装代码。Injector 文件通常使用构建标签隔离:

//go:build wireinject

package main

import "github.com/google/wire"

func InitializeService() (*Service, error) {
    wire.Build(NewDB, NewRepository, NewService)
    return nil, nil
}

安装并运行生成器:

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

Wire 会生成包含实际构造调用的 wire_gen.go。生成结果应提交到版本控制,使未安装 Wire 的环境也能正常构建。

Dig

Dig 使用 Provide 注册构造函数,再通过 Invoke 解析并使用依赖。

container := dig.New()

container.Provide(NewDB)
container.Provide(NewRepository)
container.Provide(NewService)

err := container.Invoke(func(service *Service) {
    run(service)
})

缺少依赖、重复提供同一类型或出现循环依赖时,错误会在容器解析依赖图时暴露,而不是由编译器直接指出。应把容器限制在程序入口,避免将它作为 Service Locator 传入业务代码。

Fx

Fx 在 Dig 之上提供模块、启动和停止钩子,以及操作系统信号处理。

func main() {
    fx.New(
        fx.Provide(
            NewDB,
            NewRepository,
            NewService,
        ),
        fx.Invoke(run),
    ).Run()
}

需要管理资源生命周期时,通过 fx.Lifecycle 注册钩子:

func NewServer(lc fx.Lifecycle, service *Service) *http.Server {
    server := &http.Server{Addr: ":8080"}

    lc.Append(fx.Hook{
        OnStart: func(ctx context.Context) error {
            go server.ListenAndServe()
            return nil
        },
        OnStop: func(ctx context.Context) error {
            return server.Shutdown(ctx)
        },
    })

    return server
}

选择建议

  • 初始化关系简单时使用手动组装,不要为了形式统一而引入容器。
  • 已有 Wire 项目可以继续使用生成代码,但应评估迁移路径。
  • 只需要依赖容器时使用 Dig,并将容器控制在组合根中。
  • 需要模块化、生命周期管理和优雅退出时使用 Fx。
  • 无论选择哪种方式,构造函数都应保持可独立调用,业务逻辑不应依赖具体的注入框架。