beego控制器必须显式调用init()注入ctx等依赖,否则空指针panic;prepare()必执行但finish()需手动补;直调时render()、setstatus()等也须显式调用。

Controller 初始化必须调用 Init(),否则上下文为空
Beego 的控制器不是普通 struct,beego.Controller 里所有依赖(如 Ctx、Data、TplName)都靠 Init() 方法注入。直接 &MyController{} 然后调 Get() 会 panic:空指针访问 c.Ctx 或 c.Data。
正确做法是显式调用 Init(),传入一个真实或模拟的 *context.Context:
-
Init()第一个参数必须是非 nil 的*context.Context,哪怕只是用beego.NewContext()构造的轻量实例 - 第二、三个参数分别是控制器名(如
"UserController")和动作名(如"Get"),影响日志、模板路径等行为 - 不调
Init()就调Prepare()或Get(),框架不会自动补救
Prepare() 总在动作方法前执行,但不会自动触发 Finish()
Prepare() 是 Beego 控制器生命周期中唯一保证「每次请求必走」的钩子,无论你注册的是 GET、POST 还是自定义路由映射(如 "get:List"),只要控制器被实例化并进入处理流程,Prepare() 就一定先于动作方法执行。
但要注意:Finish() 不同——它只在 HTTP 请求完整结束、响应已写出后由框架自动调用。如果你绕过路由直调控制器(比如单元测试里手动调 Get()),Finish() 不会自动触发,需手动补上:
- 手动调用时,记得最后补一句
c.Finish(),否则Session写入、ResponseWriter刷盘可能丢失 -
Prepare()中可做权限校验、参数预处理;Finish()适合清理资源、打结束日志、写统计指标 - 如果控制器嵌套了其他中间件逻辑(如自定义
Filter),它们的执行顺序独立于Prepare()/Finish()
AutoRouter 和手动路由对生命周期无影响,但影响 controllerName/actionName 解析
无论你用 beego.Router("/user", &UserCtrl{}) 还是 beego.AutoRouter(&UserCtrl{}),控制器的生命周期方法(Init → Prepare → Get/Post → Finish)执行顺序完全一致。差异只在初始化阶段的两个字符串参数:
- AutoRouter 下,
controllerName取结构体名去掉Controller后缀的小写形式(如UserController→"user"),actionName默认为"index"或动词小写(Get()→"get") - 手动路由如
beego.Router("/api/list", &UserCtrl{}, "get:ListUsers"),actionName就是"ListUsers",controllerName仍是"user"(结构体名决定) - 这两个名字会影响
c.TplName默认值、日志输出、甚至GetControllerAndAction()返回结果
直调控制器时最容易漏掉 Render() 或 Ctx.Output.SetStatus()
绕过 HTTP 流程直调控制器方法(例如后台任务或测试),很容易忽略响应状态和渲染逻辑。比如你在 Get() 里写了 c.Data["name"] = "foo" 并设了 c.TplName = "user/show.html",但没调 c.Render(),模板根本不会渲染。
同样,若用 c.Ctx.WriteString() 或 c.Ctx.JSONResp(),要留意默认 HTTP 状态码是 200;出错时需手动设 c.Ctx.Output.SetStatus(400),否则前端收不到预期状态。
-
Render()必须在Get()/Post()内显式调用,框架不会因为设了TplName就自动触发 - 直调场景下,
c.Ctx.ResponseWriter可能是 mock 对象,SetStatus()调用有效,但实际 HTTP 头不会发出去——这点在集成测试里常被忽略 - 如果控制器里用了
Redirect(),直调时它只是改了Ctx.Output.Header,不会真正跳转,需检查逻辑是否依赖重定向副作用
生命周期本身不复杂,但每个环节都绑着上下文状态。最常出问题的不是顺序错了,而是某个环节(比如 Init() 或 Render())被跳过,而错误表现往往延迟到后续操作才暴露。











