Iris 依赖注入需严格遵循注册、注入、抽象三原则:类型必须提前声明并正确注册(如用空指针或工厂函数),控制器字段需带 inject:"" 标签且控制器类型须注册,Scoped 依赖工厂函数必须接收 iris.Context 参数,外部依赖须抽象为接口以便测试替换。

直接在 handler 或控制器里 new 一个服务实例,Iris 不会识别它为可注入项——测试时无法替换,一跑就连真实数据库或 HTTP 服务。
注册依赖前必须先声明类型,否则运行时 panic
调用 app.Register 时传值(如 app.Register(UserService{}))会导致类型推导失败;字段注入时 Iris 找不到匹配项,后续解引用直接 panic:no dependency found for *UserService。
- 正确写法是传空指针:
app.Register((*UserService)(nil)),让 Iris 反射出底层具体类型 - 工厂函数注册更安全:
app.Register(func() *sql.DB { return dbConn }),适合带初始化逻辑的依赖 - 避免用
app.Singleton[Logger](...)模糊语义,显式生命周期能减少测试中状态污染风险
控制器字段注入必须同时满足三个条件
哪怕 *UserService 已注册,UserController 的字段也不会自动填充,漏掉任一条件都会静默跳过——字段保持 nil,HTTP 处理器里一用就 panic,且无提示。
- 结构体字段必须带标签:
UserRepo UserRepointerface `inject:""`(引号不能省) - 控制器类型本身要注册:
app.RegisterController[UserController]()(注意是类型,不是实例) - 所有被注入字段的类型(如
*sql.DB、UserRepository)必须已提前注册
调试建议:在 BeginRequest 中加 if c.UserRepo == nil { panic("UserRepo not injected") } 快速暴露缺失项。
Scoped 依赖工厂函数必须接收 iris.Context 参数
想让每个请求获得新实例(比如带 trace ID 的日志器),不能只靠 iris.ScopeRequest 标记——Iris 会拒绝注册,报错“函数签名不匹配”。
- 正确签名:
app.Scoped[Logger](func(ctx iris.Context) *Logger { return NewLoggerWithRequestID(ctx) }) - 错误写法:
func() *Logger加iris.ScopeRequest,Iris 无法把当前请求上下文传进去 -
hero.Handler不支持 Scoped 依赖,测试时若用 hero 封装 handler,记得改用 Singleton 或手动传参
Mock 外部依赖必须提前抽象为接口
测试中 mock 失败、运行慢、状态污染,根本原因往往是 handler 里硬编码了 sql.Open 或 http.Get——这些没法被替换,测试只能走真实网络。
- 定义接口而非具体类型:
type UserRepointerface { FindByID(ctx iris.Context, id int) (*User, error) } - handler 只依赖该接口,生产用 MySQL 实现,测试用 struct 返回预设数据
- 时间相关逻辑抽成变量:
nowFunc := time.Now,测试时可设为固定时间点 - 别 mock
*iris.Application,它状态复杂;只 mock 入参iris.Context,用httptest驱动真实 HTTP 流程
真正卡住测试的,从来不是 Iris 的 DI 机制有多难,而是依赖是否从一开始就被设计成可插拔——注册、注入、抽象,三者缺一不可。


















