6.01 AOP简介与不同框架设计概览
Last updated
Last updated
AOP:Aspect Oriented Programming,面向切面编程.核心在于将横向关注点从业务中剥离出来.
横向关注点:与业务基本无关,但每个业务(或者说每个HandleFunc)又必须要处理的.常见的有几类:
可观测性:logging、metric和tracing
安全相关:登录、鉴权与权限控制
错误处理:例如错误页面支持
可用性保证:熔断、限流和降级等
基本上WEB框架都会设计自己的AOP方案
Beego早期的时候设计了完善的回调机制,大体上有3类:
Beego中的web.MiddleWare
,其类型为func(http.Handler) http.Handler
,入参和返回值均为GO原生的http.Handler
.注意是GO原生的http.Handler
,不是Beego框架的web.HTTPServer
由于它的入参和返回值均为GO原生的http.Handler
接口,而非Beego框架的web.HTTPServer
.这导致MiddleWare基本脱离了Beego的控制.这意味着在MiddleWare中无法对Beego框架的web.HTTPServer
进行二次封装或修改其中的字段值.换言之,使用MiddleWare就意味着使用者基本脱离了Beego的控制,也就是说这种场景下使用者绕过了Beego,直接和GO原生的net/http
包交互了.
Beego的web.HTTPServer
本身就是一个http.Handler
接口的实现.而使用MiddleWare就意味着用户需要自己再实现一遍GO原生的http.Handler
接口,这个过程相当于用户自己定义了一个Beego的web.HTTPServer
,然后通过MiddleWare将用户自己实现的web.HTTPServer
与Beego的web.HTTPServer
结合在一起.
注:因为Beego的web.HTTPServer
组合了Beego的web.ControllerRegister
,而web.ControllerRegister
又实现了GO原生的http.Handler
接口.因此说Beego的web.HTTPServer
本身就是一个Handler.
正常来讲,Beggo的HTTPServer都是最后一个,作为next传入到web.MiddleWare
的入参中.然后web.MiddleWare
再返回一个自己实现的http.Handler
.这里听不懂没关系,我也没听懂,后边实现了就知道了.
web.FilterFunc
允许用户注册不同阶段运行的Filter.这些Filter都是单向的,不是环绕式的.
实际上这个类型就是web.HandleFunc
的别名.
所谓不同阶段,指的是:
所谓单向的,是指你可以注册多个Filter,但是这些Filter只会在某个具体的时间点(或者可以说是具体的阶段)上执行.而这个具体的阶段一旦执行完成,就不会再往回了.你可以认为BeforeStatic->BeforeRouter->BeforeExec->AfterExec->FinishRouter
是一路走下来的,不会回头的.因此说Filter是单向的
web.FilterChain
可以看做是一种可利用Beego内部数据的MiddleWare.它将Filter
组织成链,每一个Filter
都要考虑调用下一个Filter
,否则就会终端执行.同时,这些Filter
也失去了指定阶段运行的能力.
可以看到,其入参和返回值类型均为web.FilterFunc
apiauth.APISecretAuth()
函数用于检查能否将一个AppID转化为一个AppSecret,完成鉴权的过程
opentracing.FilterChain()
用于在链路追踪的实现过程中,初始化span并为span设定一些值
AOP是最为简单,最为方便扩展的.所以作为一个中间件设计者,并不是提供越多实现越好,更好的做法是只提供绝大多数用户会用得到,且各个不同实现用起来都没什么区别的实现.
GIN的设计稍微不同.在Beego中,FilterChain
是通过主动调用web.FilterFunc()
的方式,实现从一个web.FilterFunc()
执行到另一个web.FilterFunc()
的.相当于每一个web.FilterFunc()
自由决定要不要调用下一个web.FilterFunc()
.
GIN采用的是半集中式设计,由Context
负责调度.但实际上也是HandlerFunc
在自己的函数体内主动调用下一个HandlerFunc
gin.Context.handlers
的类型为gin.HandlersChain
注意,gin.HandlersChain
的实现如下:
可以看到gin.HandlersChain
是一个slice;而Beego中的web.FilterChain
是一个单一的函数.这是两者实现之间的区别.
在GIN中,gin.HandlersChain
被定义在了Context
抽象层级上,而Context
中定义了gin.Context.Next()
(控制执行下一个HandlerFunc)方法和gin.Context.Abort()
(控制终端后续的HandlerFunc)等方法.这些方法用于控制Context.handlers
中,HandlerFunc是否执行.
之所以说GIN的AOP方案是半集中式的,是因为虽然是HandlersChain
中的每个HandlerFunc
在自己的函数体内主动调用下一个HandlerFunc
,但是是否执行下一个HandlerFunc
,需要靠Context.Next()
和Context.Abort()
(本质上是Context.index
)来控制的.
完全的集中式是由一个中央管理器之类的统一调度各种Filter或者Handler是否执行,在中间件设计里面不常用.因为每个路由可能都有其自己要执行或不执行的Handler.
在ginmetrics.Monitor.monitorInterceptor()
方法的实现中可以看到这一行:ctx.Next()
.这也就是上文所说的每个HandlerFunc
在自己的函数体内主动调用下一个HandlerFunc
.
在ginhttp.Middleware()
函数中也能看到c.Next()
这种调用方式.
Echo的echo.MiddlewareFunc
其实现如下:
可以看到和Beego的FilterChain基本相同,也是依赖于echo.MiddlewareFunc
返回的echo.HandlerFunc
主动调用下一个echo.HandlerFunc
.
middleware.CSRFWithConfig()
函数中,也可以看到next(c)
这种代码,思路上也是每个HandlerFunc
在自己的函数体内主动调用下一个HandlerFunc
Iris的AOP设计方案与Beego的方案本质上没有区别,只是换了个名字,以及构造方式上有差别.
Iris中的router.WrapperFunc
本质上和Beego的Filter是相同的.
router.makeWrapperFunc()
函数负责构造WrapperFunc链.构造方式上来讲,核心思路也是每个WrapperFunc
在自己的函数体内主动调用下一个WrapperFunc
:
可以看到,除了GIN之外,Beego、Echo、Iris都是一样的.
有一种观点认为,GIN的那种将责任链放在Context上的设计是不如Beego、Echo、Iris的.猜测GIN的设计是为了在请求粒度上控制责任链中的每个Handler是否执行.但是正常情况下可能并不太需要在请求粒度上控制,通常而言在路由粒度上控制责任链中的每个Handler是否执行即可.
换言之,通常而言,2个请求命中了同一个路由,那么对于这个路由而言,它要执行的Handler就是相同的.而GIN可以做到的是:2个请求命中同一个路由,但这2个请求分别要执行的Handler是不同的.
个人经验上来讲,这个控制粒度精确到路由级别已经是足够用了.
我们后续实现的控制粒度在Server上,而作业需要改写为控制粒度在路由级别.