golang – middleware设计模式
在开发框架中应用了大量的设计模式,其中middleware中间件模式就是一种常见的实用模式。
下面通过一个简短的例子,给大家分享一下如何实现中间件模式。
模拟请求
1 2 3 4 5 6 |
type HandleFunc func(*Request) type Request struct { index int middlewares []HandleFunc // 中间件 + 处理函数 } |
做web开发肯定需要与请求打交道,所以这里设计一个request结构。
从代码可以看出,中间件模式实现时就是N个回调函数组成的数组,当一个request到来之后需要依次经过每一个中间件函数的加工与处理,最终request才到达用户编写的业务处理函数。
大家应该会问,业务处理函数保存在哪个字段呢?其实业务处理函数也是中间件,只不过它放在middlewares数组的末尾,最后才会被调用。
所谓的”中间”件,意思就是这些回调函数夹在入口和业务处理函数之间被调用,代表了执行的位置。
创建新请求
我们没有web环境,所以提供一个方法可以模拟创建了一个新请求:
1 2 3 4 5 6 7 8 |
// 生成请求 func NewRequest() (request *Request) { request = &Request{ index: 0, middlewares: make([]HandleFunc, 0), } return } |
这个请求还没注册中间件函数和业务处理函数,middlewares数组为空。
index字段的作用稍后解释。
注册中间件到请求
请求到来后,需要依次经过中间件的处理,因此我们接下来提供1个方法注册中间件到request上下文中。
下面的函数将注册中间件函数到middlewares数组,注册的顺序就是最终中间件被执行的顺序。
1 2 3 4 5 6 |
// 注册中间件 func (request *Request) RegisterMiddlewares(middlewares ...HandleFunc) { for _, mid := range middlewares { request.middlewares = append(request.middlewares, mid) } } |
执行请求处理
中间件注册完成后,我们就要执行请求处理了。
request会依次经过middlewares数组中各个回调函数的处理,但是注意中间件模式和插件模式是完全不同的,下面我们看看中间件模式具体是怎么实现的。
1 2 3 4 5 6 7 8 9 10 |
// 执行中间件 func (request *Request) Next() { index := request.index if index >= len(request.middlewares) { return } request.index++ request.middlewares[index](request) } |
这里我们终于看到了request.index字段的用处。
中间件模式是递归的,这是与插件模式的重要区别。
第一个中间件函数的实现中,需要递归的调用request.Next唤起下一个中间件函数,如此不断的递归向后,直到request.index到达request.middlewares的末尾,则递归终止。
递归的好处就是靠前的中间件是包裹着靠后的中间件的,这种自顶向下的结构可以实现非常灵活的框架设计。(这里我想补充一张图片,会更加直观)
实际测试
我的测试主程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func main() { // 假设来了一个请求 request := go_middlewares.NewRequest() // 注册了一些中间件 request.RegisterMiddlewares(Logger, Recovery, func(request *go_middlewares.Request) { // 业务处理函数作为中间件的最后一环 fmt.Println("我是业务逻辑") }) // 然后开始处理请求 request.Next() } |
我给request注册了2个中间件:
- Logger:用于打印请求日志
- Recovery:用于捕获业务代码中抛出的panic异常,避免程序崩溃
在最后是一个业务处理函数,里面实现业务逻辑。
中间件的执行关系是自顶向下的,递归包含的:
1 2 3 4 5 |
[ Logger ] | [ recovery ] | [业务] |
那么我们看看Logger干了什么:
1 2 3 4 5 6 |
// 日志中间件 func Logger(request *go_middlewares.Request) { fmt.Println("请求开始") // before 下一个中间件 request.Next() fmt.Println("请求结束") // after 下一个中间件 } |
它排在request.middlewares数组首位,首先被request.Next()唤起。
它在调用下一个middleware之前打印了一行日志,在下一个middleware返回之后打印了另外一行日志。
然后看看Recovery干了什么:
1 2 3 4 5 6 7 8 |
// panic捕获中间件 func Recovery(request *go_middlewares.Request) { defer func() { recover() fmt.Println("我确保panic被捕获") }() request.Next() } |
在调用下一个中间件之前,首先defer注册了一个延迟函数。
也就是说即便request.Next()执行的后续middlewares抛出了panic异常,defer函数也一定在离开Recovery函数前执行,从而可以通过runtime的recover函数捕获panic异常,避免panic异常继续向上抛出。
因此Logger中间件中是不可能遇到panic异常的,因为已经被下层的Recovery捕获了。
最后
完整代码我放在了这里:https://github.com/owenliang/go-middlewares
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~

2 thoughts on “golang – middleware设计模式”