Go中责任链模式核心是函数链式调用与中间件式委托,关键在于显式调用next.ServeHTTP()或next(ctx)移交控制权,而非类继承结构;常用http.Handler实现Web中间件链,或自定义ChainFunc处理业务逻辑。

什么是 Go 中的责任链模式核心结构
Go 没有类继承和抽象方法,所以责任链不能照搬 Java 那套 Handler 接口 + setNext() 的写法。它的本质是「函数链式调用 + 中间件式委托」:每个处理单元接收一个 http.Handler 或自定义上下文,决定是否继续传递请求。
关键不在“链”的形态,而在「控制权移交」——上一环调用 next.ServeHTTP() 或 next(ctx) 才算真正把请求往下传。
用 http.Handler 实现 Web 请求链(最常用场景)
这是生产环境最稳妥的做法,天然兼容 net/http 生态,中间件可复用(如 gorilla/mux、chi 都基于此)。
- 每个中间件是一个闭包,接收
http.Handler并返回新http.Handler - 必须显式调用
next.ServeHTTP(w, r),否则链就断了 - 顺序很重要:先注册的中间件在链头,后注册的在链尾
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("request: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r) // ← 不写这行,后续中间件和最终 handler 都不会执行
})
}
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return // ← 提前返回,不调用 next,链在此终止
}
next.ServeHTTP(w, r)
})
}
// 使用:链式组合
handler := loggingMiddleware(authMiddleware(http.HandlerFunc(homeHandler)))
http.ListenAndServe(":8080", handler)
用函数类型实现通用责任链(脱离 HTTP 场景)
当处理的是内部业务逻辑(比如审批流、数据校验、事件分发),更适合定义一个可组合的函数链:
立即学习“go语言免费学习笔记(深入)”;
- 定义
ChainFunc类型为func(context.Context) error - 每个环节接收
ctx和next ChainFunc,自行决定是否调用next(ctx) - 避免用全局变量或共享状态,所有数据通过
context.WithValue()传递(但要克制)
type ChainFunc func(context.Context) error
func WithValidation(next ChainFunc) ChainFunc {
return func(ctx context.Context) error {
data := ctx.Value("data").(string)
if len(data) == 0 {
return errors.New("empty data")
}
return next(ctx)
}
}
func WithPersistence(next ChainFunc) ChainFunc {
return func(ctx context.Context) error {
// save to DB...
return next(ctx)
}
}
// 组装
chain := WithValidation(WithPersistence(func(ctx context.Context) error {
fmt.Println("done")
return nil
}))
err := chain(context.WithValue(context.Background(), "data", "ok"))
容易踩的坑:中断、panic、ctx 超时与循环引用
责任链不是语法糖,是控制流设计,错一处就全链失效:
- 忘记调用 next:最常见错误,导致后续环节完全静默,日志里也看不到痕迹
-
panic 没 recover:一个中间件 panic 会直接崩掉整个 HTTP server,建议在顶层中间件加
defer/recover -
ctx 超时未传递:如果上游传入
ctx.WithTimeout(),每个环节都要用该ctx调用下游,否则超时失效 -
闭包捕获变量错误:循环中创建中间件时,别直接用循环变量(如
for _, m := range mw { chain = m(chain) }),要用临时变量或索引避免引用同一地址
链越长,调试越难。上线前务必用真实请求路径覆盖「正常流转」「提前中断」「panic 触发」三种情况。










