Go-Web框架中AOP方案的如何实现方式

写在前面 最近不是在跟兔兔的七天系列嘛,目前是跟到了Web框架(好吧,这才是刚开始)。关于Web框架这一块,兔兔的文章是跟完了,也做完了,现在是在做总结。总结到

写在前面

最近不是在跟兔兔的七天系列嘛,目前是跟到了Web框架(好吧,这才是刚开始)。关于Web框架这一块,兔兔的文章是跟完了,也做完了,现在是在做总结。总结到AOP方案的时候,就有点蒙圈了,所以打算写下这篇文章记录总结两种AOP方案。

Gin的AOP实现方案

其实兔兔的AOP方案和Gin的AOP方案很相似。都是通过下标控制一个中间件进入到下一个中间件的。当下标大于中间件的个数的时候,整个中间件就会往回执行

image.png

特点

  • 中间件是是现在上下文中的
  • 通过下标进入下一个中间件

具体看下Gin的实现源码

image.png

具体看下兔兔的实现源码

image.png

其实是很类似的。对于这个方法,理解起来是没什么难度的。下面我就讲讲这个AOP方案的实现流程吧。

中间件注册是通过Use方法,这个方法是RouterGroup路由组提供的,在Gin中实际是抽象出了一个接口

image.png

image.png

2. 被注册的中间件是保存在路由组的属性中,上图中圈住的部分

3. 在ServerHTTP方法中匹配命中的路由视图函数符合条件的中间件

image.png

4. 将命中的视图函数添加到中间件列表中

image.png

5. 执行Next方法。每当中间件函数中有Next方法,就会再一次进入到Next方法,由于选取要执行的中间件是通过c.index控制的,每次进来都会自加1。当所有的中间件都执行完了,index的值就超过了中间件的个数。也就是退出了递归。递归最底层的方法都执行完成了,就开始一层一层返回了。

image.png

责任链制的AOP实现原理

对于责任链制的AOP方案,原理和洋葱模式是一样,(顺带提一下,Gin的设计模式叫做洋葱模式)。它的任务是一直构建"视图函数",最终构建成这样的形式

func m() {
   fmt.Println("coming middleware1...")
   func() {
      fmt.Println("coming middleware2...")
      func() {
         fmt.Println("coming middleware3...")
         func() {
            fmt.Println("coming middleware4...")
            func() {
               fmt.Println("coming middleware5...")
               func() {
               }()
               fmt.Println("outing middleware5...")
            }()
            fmt.Println("outing middleware4...")
         }()
         fmt.Println("outing middleware3...")
      }()
      fmt.Println("outing middleware2...")
   }()
   fmt.Println("outing middleware1...")
}

但是上述这种形式太不优雅了,我们就使用一个责任链的设计模式来实现、优化,但是精髓就是构建出这种样子。

image.png

注意

  • 我们需要对中间件进行一个包装,就是说对中间件的函数签名进行一个包装
  • 我们的视图函数不用变
  • 可以理解的是中间件函数就是生成一个视图函数,只不过生成的视图函数嵌入了一些别的通用逻辑
// 视图函数签名
type HandleFunc func(ctx *Context)
// 中间件函数签名
type Middleware func(next HandleFunc) HandeFunc

下文我统一将中间函数和命中的视图函数叫做中间件。不过命中的视图函数会加上特殊二字

中间件函数签名解释一下:

  • 参数next是下一次需要的中间件逻辑
  • 返回值是一个特殊的中间件,这个就是当前这个中间件的逻辑

具体的一个中间件示例代码

func Logger() Middleware {
   return func(next HandleFunc) HandleFunc {
       return func(ctx *Context){
           fmt.Println("请求来了")
           next(ctx)
           fmt.Println("请求走了")
       }
   }
}

image.png

解释示例代码:

  • Logger函数返回一个Middleware函数。返回一个视图函数构造器
  • fmt.Println("请求来了")fmt.Println("请求走了")两行代码分别嵌在next下一个需要执行的中间件逻辑

上述已经讲完了关于责任链制的AOP方法的具体原理。但是到这里还不够,这只是原理,还没有和我们的框架进行适配。接下来就具体讲讲怎么和咱们的框架进行适配。

责任链制的AOP方案应用

首先还是需要定义好视图函数、中间件函数的签名

// 视图函数签名
type HandleFunc func(ctx *Context)
// 中间件函数签名
type Middleware func(next HandleFunc) HandeFunc

我们的AOP是集成在server层面上的,在Gin中,AOP是集成在Context上下文层面上面的。其实这个没有多大的区别的,无非是设计者的设计思想而已。

说我们的AOP是集成在server层面其实还不太准确,准确来说是在路由组RouterGroup上的。

路由组RouterGroup定义一个属性保存当前组的所有中间件

image.png

2. 路由组RouterGroup提供一个方法Use注册中间件

image.png

3. 匹配路由的时候需要找出当前路由所属哪个组,并将其所有的中间件抽离出来

image.png

4. 组装中间件

在组装中间价的时候,我们需要注意:

  • 我们注册的中间件是有顺序的
  • 我们执行的中间件也是要有顺序的
  • 先注册先执行前半部分

image.png

基于上述的注意事项,我们组装的中间件应该是从后往前组装的。这里需要花点时间想想

我们可以这样想,如果是按照正向的顺序遍历middlewares的话,最后的handler应该是最后一个注册的中间件才对。这和我们的期待是完全相反的,从这也就能解释为什么我们需要从后往前组装中间件了。只有这样,最后handler才是我们第一个注册的中间件方法。

最后就执行handler方法即可。

image.png

总结

  • 基于洋葱模型的AOP方案和基于责任链制的AOP方案本质没有区别
  • Gin的AOP是集成在上下文中,我们的是集成在RouterGroup上。两种方式没有区别

 到此这篇关于Go-Web框架中AOP方案的实现方式的文章就介绍到这了,更多相关Go AOP实现内容请搜索好代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持好代码网!

标签: Go AOP