go语言装饰器需用高阶函数显式实现,典型签名是func(context.context) error,必须严格透传ctx、defer cancel()、检查ctx.done(),链式顺序决定执行流,禁用反射而推荐泛型方案。

Go 语言没有 @decorator 语法,所有“装饰器函数”都是显式编写的高阶函数,签名不匹配、漏调 next.ServeHTTP、忘记 defer cancel(),它就直接失效。
func(context.Context) error 类型的装饰器怎么写
这是日志、超时、重试等横切逻辑最通用的签名,类型安全、可链式叠加、天然支持取消信号。
- 必须返回和输入完全一致的函数类型,否则无法赋值或传入期望位置
-
context.WithTimeout返回的cancel函数必须用defer调用,否则 goroutine 泄漏 - 被装饰的原函数内部也得检查
ctx.Done(),否则超时后仍继续执行 - 示例:
func withTimeout(f func(context.Context) error, d time.Duration) func(context.Context) error { return func(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, d) defer cancel() return f(ctx) } }
func(http.Handler) http.Handler 中间件为什么总出错
HTTP 中间件是 Go 里最常用也最容易翻车的装饰器场景,错误往往不是逻辑写错,而是类型或调用链断掉。
- 闭包返回值不是
http.Handler类型,必须用http.HandlerFunc显式转换 - 中间件内部必须显式调用
next.ServeHTTP(w, r),漏掉这句请求就静默丢弃 - 顺序决定执行流:写成
LoggingMiddleware(AuthMiddleware(h)),实际是 Logging 先进、最后出 - 别在中间件里读
r.Body或调用r.ParseForm(),会消耗 body,下游 handler 读不到数据
多个装饰器嵌套时顺序为什么重要
装饰器叠加不是并行生效,而是形成一个执行栈,外层先执行、内层后执行,资源管理和错误传播都受此影响。
-
withTimeout(withRetry(f))表示整体超时,重试过程共享同一个 deadline -
withRetry(withTimeout(f))表示每次重试都带独立 timeout,可能反复超时再重试 - panic 捕获必须在最内层(比如
WithRecovery放在链尾),否则外层 panic 不会被捕获 - 用
Chain(WithLogging, WithTimeout(5*time.Second), WithRetry(3))(h)可避免括号嵌套过深,且语义更清晰
泛型装饰器比反射方案好在哪
有人想“一次适配所有函数签名”,于是用 reflect.MakeFunc,但真实项目里基本没人这么干。
- 反射丢失编译期类型检查,
go vet和静态分析失效,IDE 跳不到原函数 - 性能差一到两个数量级,高频调用下(如每秒万级请求)装饰器本身就能拖垮服务
- 泛型方案更安全高效,例如:
WithLogCtx[T any](f func(context.Context) (T, error)) - 泛型让编译器自动推导参数类型,既免重复造轮子,又保业务逻辑不被侵入
真正容易被忽略的不是怎么写装饰器,而是谁负责清理资源、谁响应取消、谁记录错误链——这些细节不显眼,但一出问题就是 goroutine 泄漏或静默失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











