Go装饰器需手动函数组合,最稳签名是func(context.Context) error,必须保持类型一致,泛型优于反射,多层装饰顺序影响语义,HTTP中间件须显式转发ServeHTTP并正确管理资源生命周期。

Go 没有 @decorator 语法,所有“包装”都是手动函数组合——写错签名、漏调 next()、忘了 defer cancel(),它就直接失效,不是报错,而是静默坏掉。
func(context.Context) error 是最稳的装饰器签名
日志、超时、重试、限流这些横切逻辑,基本都围绕这个签名展开。它够通用,又能天然透传取消信号和超时控制。
- 必须保持输入输出类型完全一致,否则无法赋值给同名变量或传入期望该类型的参数位置
- 别用
func() error包装需要上下文的业务,否则一加withTimeout就 panic:context 相关操作(如http.NewRequestWithContext)会因 nil ctx 崩溃 - 如果业务函数返回值不止
error(比如func(ctx context.Context) (string, error)),泛型包装器比反射安全得多,也快得多
多个装饰器叠加时顺序决定行为语义
执行顺序是外层先触发,类似函数调用栈:最左边的装饰器最先执行,最右边的最后执行。
-
withTimeout(withRetry(f)):整个重试过程受统一超时约束;反过来withRetry(withTimeout(f)),每次重试都带独立 timeout,可能反复拉起 goroutine -
WithRecovery(WithLogging(handler)):panic 能被捕获;但写成WithLogging(WithRecovery(handler)),recover 就包不住 logging 里的 panic - HTTP 中间件链中,
auth必须在logging之后?不,是必须在body 解析前—— 否则r.ParseForm()在 auth 里被调用一次,logging 再读就空了
http.Handler 类型的装饰必须显式转发 ServeHTTP
标准库的 func(http.Handler) http.Handler 模式,就是接口级装饰的落地。关键不是“套壳”,而是“转发”。
立即学习“go语言免费学习笔记(深入)”;
- 漏掉
h.next.ServeHTTP(w, r),请求就卡死在当前中间件,无错误、无响应、无日志 - 别写
type authHandler struct { next *authHandler }—— 这是循环引用,编译都过不去;应该用next http.Handler - 把普通函数转成
http.Handler,必须用http.HandlerFunc(f)显式转换;直接传f会类型不匹配
泛型包装器比反射更实用,也更容易维护
有人想“一次适配所有签名”,于是用 reflect.MakeFunc。真实项目里几乎没人这么干。
- 反射丢失编译期类型检查,IDE 跳不到原函数,
go vet和静态分析全失效 - 性能差一到两个数量级,高频调用下(比如每秒万级请求),日志装饰器本身就能拖垮服务
- 推荐按常用签名写几个泛型版本:
WithLogCtx[T any](f func(context.Context) (T, error))、WithRetryCtx[T any](f func(context.Context) (T, error), max int)
最容易被忽略的不是怎么写,而是资源生命周期管理:context.WithTimeout 返回的 cancel 函数必须 defer,否则超时前 panic 就泄漏;限流器里必须用 limiter.Wait(ctx),不能用 context.Background(),否则上游 cancel 信号进不来。


















