Go-Web框架中AOP方案的實(shí)現(xiàn)方式
寫在前面
最近不是在跟兔兔的七天系列嘛,目前是跟到了Web框架(好吧,這才是剛開始)。關(guān)于Web框架這一塊,兔兔的文章是跟完了,也做完了,現(xiàn)在是在做總結(jié)。總結(jié)到AOP方案的時候,就有點(diǎn)蒙圈了,所以打算寫下這篇文章記錄總結(jié)兩種AOP方案。
Gin的AOP實(shí)現(xiàn)方案
其實(shí)兔兔的AOP方案和Gin的AOP方案很相似。都是通過下標(biāo)控制一個中間件進(jìn)入到下一個中間件的。當(dāng)下標(biāo)大于中間件的個數(shù)的時候,整個中間件就會往回執(zhí)行
特點(diǎn)
- 中間件是是現(xiàn)在上下文中的
- 通過下標(biāo)進(jìn)入下一個中間件
具體看下Gin的實(shí)現(xiàn)源碼
具體看下兔兔的實(shí)現(xiàn)源碼
其實(shí)是很類似的。對于這個方法,理解起來是沒什么難度的。下面我就講講這個AOP方案的實(shí)現(xiàn)流程吧。
中間件注冊是通過Use
方法,這個方法是RouterGroup
路由組提供的,在Gin中實(shí)際是抽象出了一個接口
2. 被注冊的中間件是保存在路由組的屬性中,上圖中圈住的部分
3. 在ServerHTTP
方法中匹配命中的路由視圖函數(shù)、符合條件的中間件。
4. 將命中的視圖函數(shù)添加到中間件列表中
5. 執(zhí)行Next
方法。每當(dāng)中間件函數(shù)中有Next
方法,就會再一次進(jìn)入到Next
方法,由于選取要執(zhí)行的中間件是通過c.index
控制的,每次進(jìn)來都會自加1。當(dāng)所有的中間件都執(zhí)行完了,index的值就超過了中間件的個數(shù)。也就是退出了遞歸。遞歸最底層的方法都執(zhí)行完成了,就開始一層一層返回了。
責(zé)任鏈制的AOP實(shí)現(xiàn)原理
對于責(zé)任鏈制的AOP方案,原理和洋蔥模式是一樣,(順帶提一下,Gin的設(shè)計模式叫做洋蔥模式)。它的任務(wù)是一直構(gòu)建"視圖函數(shù)",最終構(gòu)建成這樣的形式
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...") }
但是上述這種形式太不優(yōu)雅了,我們就使用一個責(zé)任鏈的設(shè)計模式來實(shí)現(xiàn)、優(yōu)化,但是精髓就是構(gòu)建出這種樣子。
注意
- 我們需要對中間件進(jìn)行一個包裝,就是說對中間件的函數(shù)簽名進(jìn)行一個包裝
- 我們的視圖函數(shù)不用變
- 可以理解的是中間件函數(shù)就是生成一個視圖函數(shù),只不過生成的視圖函數(shù)嵌入了一些別的通用邏輯
// 視圖函數(shù)簽名 type HandleFunc func(ctx *Context) // 中間件函數(shù)簽名 type Middleware func(next HandleFunc) HandeFunc
下文我統(tǒng)一將中間函數(shù)和命中的視圖函數(shù)叫做中間件。不過命中的視圖函數(shù)會加上特殊二字
中間件函數(shù)簽名解釋一下:
- 參數(shù)
next
是下一次需要的中間件邏輯 - 返回值是一個特殊的中間件,這個就是當(dāng)前這個中間件的邏輯
具體的一個中間件示例代碼
func Logger() Middleware { return func(next HandleFunc) HandleFunc { return func(ctx *Context){ fmt.Println("請求來了") next(ctx) fmt.Println("請求走了") } } }
解釋示例代碼:
Logger
函數(shù)返回一個Middleware
函數(shù)。返回一個視圖函數(shù)構(gòu)造器fmt.Println("請求來了")
和fmt.Println("請求走了")
兩行代碼分別嵌在next
下一個需要執(zhí)行的中間件邏輯
上述已經(jīng)講完了關(guān)于責(zé)任鏈制的AOP方法的具體原理。但是到這里還不夠,這只是原理,還沒有和我們的框架進(jìn)行適配。接下來就具體講講怎么和咱們的框架進(jìn)行適配。
責(zé)任鏈制的AOP方案應(yīng)用
首先還是需要定義好視圖函數(shù)、中間件函數(shù)的簽名
// 視圖函數(shù)簽名 type HandleFunc func(ctx *Context) // 中間件函數(shù)簽名 type Middleware func(next HandleFunc) HandeFunc
我們的AOP是集成在server層面上的,在Gin中,AOP是集成在Context上下文層面上面的。其實(shí)這個沒有多大的區(qū)別的,無非是設(shè)計者的設(shè)計思想而已。
說我們的AOP是集成在server層面其實(shí)還不太準(zhǔn)確,準(zhǔn)確來說是在路由組RouterGroup
上的。
路由組RouterGroup
定義一個屬性保存當(dāng)前組的所有中間件
2. 路由組RouterGroup
提供一個方法Use
注冊中間件
3. 匹配路由的時候需要找出當(dāng)前路由所屬哪個組,并將其所有的中間件抽離出來
4. 組裝中間件
在組裝中間價的時候,我們需要注意:
- 我們注冊的中間件是有順序的
- 我們執(zhí)行的中間件也是要有順序的
- 先注冊先執(zhí)行前半部分
基于上述的注意事項(xiàng),我們組裝的中間件應(yīng)該是從后往前組裝的。這里需要花點(diǎn)時間想想
我們可以這樣想,如果是按照正向的順序遍歷middlewares
的話,最后的handler
應(yīng)該是最后一個注冊的中間件才對。這和我們的期待是完全相反的,從這也就能解釋為什么我們需要從后往前組裝中間件了。只有這樣,最后handler
才是我們第一個注冊的中間件方法。
最后就執(zhí)行handler
方法即可。
總結(jié)
- 基于洋蔥模型的AOP方案和基于責(zé)任鏈制的AOP方案本質(zhì)沒有區(qū)別
- Gin的AOP是集成在上下文中,我們的是集成在
RouterGroup
上。兩種方式?jīng)]有區(qū)別
到此這篇關(guān)于Go-Web框架中AOP方案的實(shí)現(xiàn)方式的文章就介紹到這了,更多相關(guān)Go AOP實(shí)現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用dep 配置golang 開發(fā)環(huán)境的操作方法
下面小編就為大家?guī)硪黄褂胐ep 配置golang 開發(fā)環(huán)境的操作方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09從零封裝Gin框架實(shí)現(xiàn)日志初始化及切割歸檔功能
這篇文章主要為大家介紹了從零封裝Gin框架實(shí)現(xiàn)日志初始化及切割歸檔功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01Go泛型實(shí)戰(zhàn)教程之如何在結(jié)構(gòu)體中使用泛型
這篇文章主要介紹了Go泛型實(shí)戰(zhàn)教程之如何在結(jié)構(gòu)體中使用泛型,根據(jù)Go泛型使用的三步曲提到的:類型參數(shù)化、定義類型約束、類型實(shí)例化我們一步步來定義我們的緩存結(jié)構(gòu)體,需要的朋友可以參考下2022-07-07go panic時如何讓函數(shù)返回數(shù)據(jù)?
今天小編就為大家分享一篇關(guān)于go panic時如何讓函數(shù)返回數(shù)據(jù)?,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-04-04