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 触发」三种情况。