Kratos 的依赖注入通过 wire 在编译前生成代码,而非运行时反射,使错误在 build 阶段暴露;wire 强制声明依赖关系、避免手动构造的维护困境、检测循环依赖,并要求 provider 集合、injector 函数和 //go:build wireinject 标记三者齐全。

Kratos 的依赖注入不是靠运行时反射,而是用 wire 在编译前生成代码——这意味着错误在 go build 阶段就能暴露,而不是等服务跑起来才 panic。
为什么必须用 wire 而不是手写初始化逻辑
手动 new 一堆对象再层层传参(比如 NewGreeter(NewMessage()))看似简单,但项目一复杂就失控:
- 每次加一个新依赖(比如数据库 client),所有上层构造函数都要改参数、改调用链
- 测试时无法轻松替换 mock 实例,因为 New 函数硬编码了具体类型
- 启动顺序隐含在调用顺序里,没人能一眼看出
greeter是否依赖了还没初始化的cache -
wire强制你把依赖关系声明为函数签名,编译器会检查是否可解、是否有循环依赖
wire.go 文件里要写什么
它不是配置文件,而是一个 Go 源文件,作用是告诉 wire “如何拼出最终的 *kratos.App”。关键三要素缺一不可:
- 一个
Provider函数集合:每个函数返回一个具体类型,比如func() *sql.DB、func() *service.GreeterService - 一个
Injector函数:接收所有需要的依赖作为参数,返回你要构建的目标(通常是*kratos.App),比如func newApp(...) - 一个
//go:build wireinject构建约束标记:让wire知道该处理这个文件,且避免被普通构建误引入
示例片段(非完整):
//go:build wireinject
package main
import (
"github.com/go-kratos/kratos/v2"
"github.com/google/wire"
)
func initApp(*kratos.Config, *kratos.Logger) *kratos.App {
panic(wire.Build(
// provider 集合
wire.Struct(new(service.GreeterService), "*"),
wire.Bind(new(biz.GreeterRepo), new(data.GreeterRepo)),
// injector
newApp,
))
}
wire 命令执行失败的常见原因
运行 wire 后报错,基本逃不出这几类:
-
no provider found for *sql.DB:你用了*sql.DB当参数,但没定义返回它的Provider函数,也没用wire.Value或wire.Struct显式提供 -
circular dependency:A 依赖 B,B 又依赖 A ——wire会在生成阶段直接报错,不会让你糊弄过去 -
cannot use ... as type ...:接口绑定错了,比如写了wire.Bind(new(Repo), new(MockRepo)),但MockRepo并没有实现Repo接口 - 忘记在
go.mod里引入github.com/google/wire/cmd/wire,导致go run github.com/google/wire/cmd/wire@latest找不到命令
生成的 wire_gen.go 能不能改
不能。它是纯生成代码,任何手动修改都会在下次 wire 运行后被覆盖。如果发现生成逻辑不对,应该回溯到 wire.go 里的 wire.Build 调用或 Provider 函数去修正。
真正容易被忽略的是:wire 不会帮你做资源释放。比如你注册了一个 *sql.DB,它被注入到多个 service 中,但 App.Close() 不会自动调用 db.Close() —— 你得在 newApp 返回前,显式把 cleanup 函数注册进 kratos.App 的 kratos.WithClose 选项里。


















