Iris 框架不提供自动依赖注入,因其轻量设计强调显式控制与零反射开销;依赖需手动创建、传递和复用,请求级依赖通过 handler 内新建并结合 ctx.Values() 实现,单例级依赖则在启动时初始化全局对象(如 *sql.DB)并显式传入。

Iris 框架本身不提供传统意义上的 MVC 分层结构或内置的“请求级/单例级依赖注入容器”——它没有 Spring 的 @Service、@Scope("prototype") 或 ASP.NET Core 的 AddScoped/AddSingleton 这类声明式生命周期管理。所谓“MVC 中的依赖注入”,在 Iris 里是手动组织 + 显式传递的,不是框架自动解析的。
为什么 Iris 没有自动依赖注入机制
Iris 是一个轻量、高性能的 Go Web 框架,设计哲学偏向显式控制和零反射开销。它不扫描 struct 标签、不解析构造函数参数类型、也不维护 Bean 容器。所有“依赖”都靠开发者自己 new、传入、复用。
- Go 语言原生不支持运行时反射注入(不像 Java/Spring 或 C#/.NET)
- Iris 的
iris.Application不提供服务注册表(如serviceContainer.Register<irepository>()</irepository>) - 所谓“控制器”,只是普通 struct;所谓“注入”,本质是字段赋值或方法参数传参
如何模拟请求级依赖(per-request)
请求级依赖的核心是:每次 HTTP 请求进来,都新建一个实例(比如数据库事务、临时缓存、上下文绑定对象)。Iris 中需靠 context.Handler 手动创建并传递。
- 不要把需要请求隔离的对象(如
*sql.Tx、session.Session)设为 controller struct 字段 - 改用 handler 函数内联创建,或通过
ctx.Values().Set()存储,再在后续中间件/处理函数中取用 - 示例:为每个请求生成唯一 traceID 并注入到 handler 链中
app.Get("/user/{id}", func(ctx iris.Context) {
traceID := uuid.NewString()
ctx.Values().Set("trace_id", traceID)
<pre class="brush:php;toolbar:false;">// 模拟请求级 repo 实例(带当前 ctx 绑定)
repo := &UserRepo{Ctx: ctx}
user, _ := repo.FindByID(ctx.Params().Get("id"))
ctx.JSON(user)})
注意:UserRepo 必须接收 iris.Context(而非全局 *sql.DB)才能实现请求隔离;否则直接复用全局 DB 就是单例行为。
如何实现单例级依赖(singleton)
单例即应用启动时初始化一次、全程复用的对象,比如数据库连接池、Redis 客户端、配置对象。Iris 中只需在 main() 或初始化函数中创建,然后作为字段传给 handler 或 controller struct。
- 典型做法:定义全局变量或封装在 app struct 中
- 避免在 handler 内部用
sql.Open或redis.NewClient—— 那会泄漏资源且失去连接复用 - 如果用了 controller struct,务必在初始化时传入单例依赖,而不是在方法里重新 new
// 全局单例
var db *sql.DB
<p>func main() {
db = initDB() // 只执行一次
app := iris.New()
app.Get("/users", usersHandler)
app.Listen(":8080")
}</p><p>func usersHandler(ctx iris.Context) {
rows, _ := db.Query("SELECT <em> FROM users") // 复用同一个 </em>sql.DB
// ...
}
</p>
若用 struct controller 模式:
type UserController struct {
DB *sql.DB // 单例依赖,由外部注入
}
<p>func (c *UserController) GetUsers(ctx iris.Context) {
rows, _ := c.DB.Query("SELECT ...") // 安全:DB 是共享的、线程安全的
}
</p>
关键点:*sql.DB 本身就是 Go 推荐的单例对象(内部含连接池),不需要额外包装 scope 控制。
容易踩的坑:混淆“struct 实例”和“依赖生命周期”
常见错误是以为给 controller struct 加个指针字段(如 DB *sql.DB)就等于“注入”,但没想清楚这个 struct 本身是谁 new 的、何时 new 的。
- 如果你每次请求都 new
&UserController{DB: db},那 controller 是请求级,但 DB 仍是单例 —— 这没问题 - 但如果你在 handler 里写
repo := NewUserRepo(),而NewUserRepo()内部又 new 了*sql.DB,那就变成每次请求建新连接池,严重泄漏 - Iris 不会帮你校验这种逻辑,一切依赖你对 Go 对象生命周期的理解
真正需要关注的不是“怎么注入”,而是“这个对象是否该被多个 goroutine 共享”以及“它的 Close() 是否只调用一次”。











