beego.run() 启动时先完成应用初始化、路由注册、中间件挂载等,再将实现http.handler的app.handlers传给http服务器;其servehttp方法统一处理请求:路由匹配→创建新控制器实例→执行prepare()→调用http方法→响应。

beego.Run() 启动时到底做了什么
它不是简单地调用 http.ListenAndServe,而是先完成整个应用对象的初始化、路由注册、中间件挂载、日志与配置加载,最后才把封装好的 app.Handlers 作为 http.Handler 传给底层服务器。
关键点在于:app.Handlers 是一个实现了 http.Handler 接口的结构体(实际是 web.App 的嵌入字段),它的 ServeHTTP 方法才是所有请求的统一入口。这个方法内部会做:路由匹配 → 构造控制器实例 → 执行 Prepare() → 调用对应 HTTP 方法函数(如 Get())→ 渲染或返回响应。
常见误区是以为 beego.Router() 注册完就“生效了”,其实它只是往全局路由表(web.BeeApp.Handlers.router)里塞规则;真正起作用的是 Run() 启动后那个持续监听的 ServeHTTP 流程。
控制器实例化发生在每次请求时,不是单例
每次 HTTP 请求到达,beego 都会通过反射创建一个新的控制器实例(比如 &UserController{}),而不是复用某个全局单例。这意味着:
-
struct字段不能存跨请求状态(如计数器、缓存 map),否则会丢失或错乱 -
Prepare()方法天然适合做请求级初始化(如权限校验、参数预处理、DB 连接绑定) - 控制器里的
web.Controller嵌入提供了c.Ctx、c.Data、c.Input等请求上下文对象,它们的生命周期严格绑定当前请求
如果你在控制器里写了 var count int 并在 Get() 中递增,那这个 count 永远是 0 —— 因为每次都是新实例。
路由匹配走的是 Radix Tree,不是正则回溯
beego 使用基数树(Radix Tree)实现路由查找,时间复杂度接近 O(k)(k 是路径长度),比线性遍历或正则全量匹配快得多。这也决定了它对路由写法有隐含约束:
- 静态路径(
/api/users)和带命名参数路径(/api/users/:id)能高效共存 - 通配符
*和正则路由(如@/file/([a-z]+).pdf)会退化为线性扫描,应慎用 - 路由顺序不重要 —— 基数树自动按最长前缀匹配,
/api/users/:id不会误匹配到/api/users/export
你可以用 bee api 生成的项目观察 routers/router.go,里面所有 web.Router() 调用最终都汇入同一棵 radix 树,而非独立的正则引擎。
日志模块的异步写入靠 channel + goroutine,但默认不丢日志
BeeLogger 的异步模式本质是:调用 Info() 等方法时,只把日志消息塞进 msgChan,由后台 goroutine 从 channel 取出并写入目标(文件、控制台等)。这避免了 I/O 阻塞主线程。
但要注意两个易忽略细节:
- channel 有容量(默认 1000 条),写满后新日志会被丢弃 —— 不是阻塞,是静默丢弃
- 进程退出前必须显式调用
logs.Close(),否则 channel 里残留的日志永远不会被消费 -
asynchronous: true配置只影响输出环节,不影响日志等级过滤和格式化逻辑
线上服务若发现偶发日志缺失,第一反应不该是查磁盘空间,而应检查 msgChan 是否持续满载,以及是否漏掉了 Close() 调用。











