为你自己学Go
  • README
  • PART01.Web框架概览
    • 1.01 Web框架概览:学习路线
    • 1.02 Web框架概览-Beego框架分析
    • 1.03 Web框架概览-GIN框架分析
    • 1.04 Web框架概览-Iris框架分析
    • 1.05 Web框架概览-Echo框架分析
  • PART02.Server
    • 2.01 Server详解与面试要点
  • PART03.路由树
    • 3.01 路由树-Beego&GIN&Echo实现与设计总结
    • 3.02 路由树-全静态匹配
    • 3.03 路由树-TDD起步
    • 3.04 路由树-静态匹配测试用例
    • 3.05 路由树-静态匹配之路由查找
    • 3.06 路由树-静态匹配之集成Server
    • 3.07 路由树-通配符匹配之路由注册
    • 3.08 路由树-通配符匹配之路由查找与测试
    • 3.09 路由树-参数路径之基本注册和查找
    • 3.10 路由树-参数路径之校验
    • 3.11 路由树-参数路径之参数值
    • 3.12 路由树-总结与面试要点
  • PART04.课后复习
    • 4.01 课后复习-Server
    • 4.02 课后复习-Route
  • PART05.Context
    • 5.01 Context-简介
    • 5.02 Context-Beego Context设计分析
    • 5.03 Context-Gin Context设计分析
    • 5.04 Context-Echo和Iris的Context设计分析
    • 5.05 Context-处理输入输出总结
    • 5.06 Context-处理输入之Body输入
    • 5.07 Context-处理输入之表单输入
    • 5.08 Context-处理输入之查询参数、路径参数和StringValue
    • 5.09 Context-处理输出
    • 5.10 Context-总结与面试要点
  • PART06.AOP
    • 6.01 AOP简介与不同框架设计概览
    • 6.02 AOP设计方案-Middleware
  • PART07.Middleware
    • 7.01 Middleware-AccessLog
    • 7.02 Middleware-Trace简介和OpenTelemetry
    • 7.03 Middleware-OpenTelemetry测试
    • 7.04 Middleware-OpenTelemetry总结
    • 7.05 Prometheus详解
    • 7.06 Middleware-Prometheus
    • 7.07 Middleware-错误页面
    • 7.08 Middleware-从panic中恢复
    • 7.09 Middleware总结和面试
  • PART08.Review
    • 8.01 课后复习-AOP
    • 8.02 课后复习-Context
    • 8.03 课后复习-Middleware-AccessLog
  • PART09.Appendix
    • 附录1.责任链模式
    • 附录2.生成器模式
    • 附录3.函数选项模式
  • xiaochengxu
    • 01.原力去水印
    • 02.KeePass密码管理:安全轻松的管理您的密码
Powered by GitBook
On this page
  • PART1. 什么是AOP?
  • PART2. Beego的AOP方案设计
  • 2.1 MiddleWare
  • 2.2 Filter
  • 2.3 FilterChain
  • 2.4 Beego中AOP方案的案例
  • PART3. GIN的AOP方案设计
  • 3.1 HandlersChain
  • 3.2 GIN中AOP方案的案例
  • PART4. Echo的AOP方案设计
  • 4.1 MiddlewareFunc
  • 4.2 Echo中AOP方案的案例
  • PART5. Iris的AOP方案设计
  • 5.1 router.WrapperFunc
  • PART6. 总结
  1. PART06.AOP

6.01 AOP简介与不同框架设计概览

Last updated 9 months ago

PART1. 什么是AOP?

AOP:Aspect Oriented Programming,面向切面编程.核心在于将横向关注点从业务中剥离出来.

横向关注点:与业务基本无关,但每个业务(或者说每个HandleFunc)又必须要处理的.常见的有几类:

  • 可观测性:logging、metric和tracing

  • 安全相关:登录、鉴权与权限控制

  • 错误处理:例如错误页面支持

  • 可用性保证:熔断、限流和降级等

基本上WEB框架都会设计自己的AOP方案

PART2. Beego的AOP方案设计

Beego早期的时候设计了完善的回调机制,大体上有3类:

2.1 MiddleWare

正常来讲,Beggo的HTTPServer都是最后一个,作为next传入到web.MiddleWare的入参中.然后web.MiddleWare再返回一个自己实现的http.Handler.这里听不懂没关系,我也没听懂,后边实现了就知道了.

2.2 Filter


// default filter execution points
const (
	// 处理静态资源之前执行的filter
	BeforeStatic = iota
	// 执行路由匹配之前执行的filter
	BeforeRouter
	// 执行业务逻辑(HandleFunc)之前执行的filter
	BeforeExec
	// 执行业务逻辑(HandleFunc)之后执行的filter
	AfterExec
	// 路由结束之后执行的filter
	FinishRouter
)

所谓单向的,是指你可以注册多个Filter,但是这些Filter只会在某个具体的时间点(或者可以说是具体的阶段)上执行.而这个具体的阶段一旦执行完成,就不会再往回了.你可以认为BeforeStatic->BeforeRouter->BeforeExec->AfterExec->FinishRouter是一路走下来的,不会回头的.因此说Filter是单向的

2.3 FilterChain

// FilterChain is different from pure FilterFunc
// when you use this, you must invoke next(ctx) inside the FilterFunc which is returned
// And all those FilterChain will be invoked before other FilterFunc
type FilterChain func(next FilterFunc) FilterFunc

2.4 Beego中AOP方案的案例

2.4.1 APISecretAuth

2.4.2 FilterChain

AOP是最为简单,最为方便扩展的.所以作为一个中间件设计者,并不是提供越多实现越好,更好的做法是只提供绝大多数用户会用得到,且各个不同实现用起来都没什么区别的实现.

PART3. GIN的AOP方案设计

3.1 HandlersChain

GIN的设计稍微不同.在Beego中,FilterChain是通过主动调用web.FilterFunc()的方式,实现从一个web.FilterFunc()执行到另一个web.FilterFunc()的.相当于每一个web.FilterFunc()自由决定要不要调用下一个web.FilterFunc().

GIN采用的是半集中式设计,由Context负责调度.但实际上也是HandlerFunc在自己的函数体内主动调用下一个HandlerFunc

注意,gin.HandlersChain的实现如下:

type HandlersChain []HandlerFunc

可以看到gin.HandlersChain是一个slice;而Beego中的web.FilterChain是一个单一的函数.这是两者实现之间的区别.

之所以说GIN的AOP方案是半集中式的,是因为虽然是HandlersChain中的每个HandlerFunc在自己的函数体内主动调用下一个HandlerFunc,但是是否执行下一个HandlerFunc,需要靠Context.Next()和Context.Abort()(本质上是Context.index)来控制的.

完全的集中式是由一个中央管理器之类的统一调度各种Filter或者Handler是否执行,在中间件设计里面不常用.因为每个路由可能都有其自己要执行或不执行的Handler.

3.2 GIN中AOP方案的案例

3.2.1 metric实现

3.2.2 tracing实现

PART4. Echo的AOP方案设计

4.1 MiddlewareFunc

// MiddlewareFunc defines a function to process middleware.
MiddlewareFunc func(next HandlerFunc) HandlerFunc

可以看到和Beego的FilterChain基本相同,也是依赖于echo.MiddlewareFunc返回的echo.HandlerFunc主动调用下一个echo.HandlerFunc.

4.2 Echo中AOP方案的案例

4.2.1 CSRF防护

PART5. Iris的AOP方案设计

5.1 router.WrapperFunc

Iris的AOP设计方案与Beego的方案本质上没有区别,只是换了个名字,以及构造方式上有差别.

func makeWrapperFunc(original WrapperFunc, wrapperFunc WrapperFunc) WrapperFunc {
	if wrapperFunc == nil {
		return original
	}

	if original != nil {
		// wrap into one function, from bottom to top, end to begin.
		nextWrapper := wrapperFunc
		prevWrapper := original
		wrapperFunc = func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
			// 下一个WrapperFunc不为空则手动调用下一个
			if next != nil {
				nexthttpFunc := http.HandlerFunc(func(_w http.ResponseWriter, _r *http.Request) {
					prevWrapper(_w, _r, next)
				})
				nextWrapper(w, r, nexthttpFunc)
			}
		}
	}

	return wrapperFunc
}

PART6. 总结

可以看到,除了GIN之外,Beego、Echo、Iris都是一样的.

有一种观点认为,GIN的那种将责任链放在Context上的设计是不如Beego、Echo、Iris的.猜测GIN的设计是为了在请求粒度上控制责任链中的每个Handler是否执行.但是正常情况下可能并不太需要在请求粒度上控制,通常而言在路由粒度上控制责任链中的每个Handler是否执行即可.

换言之,通常而言,2个请求命中了同一个路由,那么对于这个路由而言,它要执行的Handler就是相同的.而GIN可以做到的是:2个请求命中同一个路由,但这2个请求分别要执行的Handler是不同的.

个人经验上来讲,这个控制粒度精确到路由级别已经是足够用了.

我们后续实现的控制粒度在Server上,而作业需要改写为控制粒度在路由级别.

Beego中的,其类型为func(http.Handler) http.Handler,入参和返回值均为GO原生的.注意是GO原生的http.Handler,不是Beego框架的web.HTTPServer

由于它的入参和返回值均为GO原生的http.Handler接口,而非Beego框架的.这导致MiddleWare基本脱离了Beego的控制.这意味着在MiddleWare中无法对Beego框架的web.HTTPServer进行二次封装或修改其中的字段值.换言之,使用MiddleWare就意味着使用者基本脱离了Beego的控制,也就是说这种场景下使用者绕过了Beego,直接和GO原生的net/http包交互了.

Beego的本身就是一个http.Handler接口的实现.而使用MiddleWare就意味着用户需要自己再实现一遍GO原生的http.Handler接口,这个过程相当于用户自己定义了一个Beego的web.HTTPServer,然后通过MiddleWare将用户自己实现的web.HTTPServer与Beego的web.HTTPServer结合在一起.

注:因为Beego的组合了Beego的,而web.ControllerRegister又实现了GO原生的接口.因此说Beego的本身就是一个Handler.

允许用户注册不同阶段运行的Filter.这些Filter都是单向的,不是环绕式的.

实际上这个类型就是的别名.

所谓,指的是:

可以看做是一种可利用Beego内部数据的MiddleWare.它将Filter组织成链,每一个Filter都要考虑调用下一个Filter,否则就会终端执行.同时,这些Filter也失去了指定阶段运行的能力.

可以看到,其入参和返回值类型均为

函数用于检查能否将一个AppID转化为一个AppSecret,完成鉴权的过程

用于在链路追踪的实现过程中,初始化span并为span设定一些值

的类型为

在GIN中,gin.HandlersChain被定义在了Context抽象层级上,而Context中定义了(控制执行下一个HandlerFunc)方法和(控制终端后续的HandlerFunc)等方法.这些方法用于控制Context.handlers中,HandlerFunc是否执行.

在方法的实现中可以看到这一行:ctx.Next().这也就是上文所说的每个HandlerFunc在自己的函数体内主动调用下一个HandlerFunc.

在函数中也能看到c.Next()这种调用方式.

Echo的其实现如下:

函数中,也可以看到next(c)这种代码,思路上也是每个HandlerFunc在自己的函数体内主动调用下一个HandlerFunc

Iris中的本质上和Beego的Filter是相同的.

函数负责构造WrapperFunc链.构造方式上来讲,核心思路也是每个WrapperFunc在自己的函数体内主动调用下一个WrapperFunc:

web.MiddleWare
http.Handler
web.HTTPServer
web.HTTPServer
web.HTTPServer
web.ControllerRegister
http.Handler
web.HTTPServer
web.FilterFunc
web.HandleFunc
不同阶段
web.FilterChain
web.FilterFunc
apiauth.APISecretAuth()
opentracing.FilterChain()
gin.Context.handlers
gin.HandlersChain
gin.Context.Next()
gin.Context.Abort()
ginmetrics.Monitor.monitorInterceptor()
ginhttp.Middleware()
echo.MiddlewareFunc
middleware.CSRFWithConfig()
router.WrapperFunc
router.makeWrapperFunc()
学习路线