Go语言defer执行顺序与Middleware链式调用逻辑疑问
解答
首先理清两个易混淆的执行阶段
你之前的预期错误核心是把「中间件工厂函数的执行」和「中间件逻辑的执行」混为一谈了:
- 调用
endpoint.Chain(annotate("first"), annotate("second"), annotate("third"))的时候,三个annotate确实会按传参顺序依次执行,但是它们只是生成并返回了Middleware类型的函数实例,并没有执行annotate内部返回的两层匿名函数,这一步只是完成中间件链的组装,没有任何日志输出。 - 只有当你调用
e(ctx, req)的时候,才会真正执行组装好的中间件链逻辑,所有的pre、post、endpoint日志都是这一阶段输出的。
疑问1解答:为什么pre输出后没有紧跟对应的post
defer语句的触发时机是它所在的当前函数返回前,你可以把组装完的中间件调用逻辑展开成嵌套调用的形式:
// 组装完的e等价于下面的逻辑 func e(ctx context.Context, req interface{}) (interface{}, error) { fmt.Println("first pre") defer fmt.Println("first post") // 调用next也就是second的中间件处理函数 return func(ctx context.Context, req interface{}) (interface{}, error) { fmt.Println("second pre") defer fmt.Println("second post") // 调用next也就是third的中间件处理函数 return func(ctx context.Context, req interface{}) (interface{}, error) { fmt.Println("third pre") defer fmt.Println("third post") // 调用next也就是myEndpoint return func(ctx context.Context, req interface{}) (interface{}, error) { fmt.Println("my endpoint!") return struct{}{}, nil }(ctx, req) }(ctx, req) }(ctx, req) }
执行的时候外层函数要等内层嵌套函数全部执行完返回之后,才会触发自己的返回逻辑:
- 进入最外层函数打印
first pre,注册defer,然后调用内层second的函数,此时最外层函数还没有返回,所以first post不会触发 - 同理进入second函数打印
second pre,注册defer,调用third函数,second函数未返回,second post不会触发 - 进入third函数打印
third pre,注册defer,调用myEndpoint,third函数未返回,third post不会触发 - 直到myEndpoint执行完打印
my endpoint!并返回,才会从最内层开始逐层向外返回,每层函数返回前触发自己注册的defer,自然不会出现pre之后紧跟post的情况。
疑问2解答:为什么post输出是逆序的
你这里有个误解:pre按声明顺序输出,说明的是越先声明的中间件,它的处理函数越在外层,执行越早,不是内层函数先执行。
整个执行和返回的逻辑符合栈的「后进先出」规则:
- 执行阶段:外层函数先执行,所以pre的顺序是
first → second → third,和声明顺序一致 - 返回阶段:最内层的函数最先拿到返回结果,所以最先返回,它注册的defer最先触发,所以post的顺序是
third → second → first,正好和pre顺序相反
Chain的反转逻辑只是用来把传参的第一个中间件包装成最外层,保证先传的中间件先执行pre逻辑,和你说的annotate先求值、pre顺序的表现完全不矛盾。
内容的提问来源于stack exchange,提问作者dashenswen
相关产品推荐
相关产品推荐

