做電商網(wǎng)站賺錢(qián)嗎關(guān)鍵詞排名優(yōu)化顧問(wèn)
【Gin】深度解析:在Gin框架中優(yōu)化應(yīng)用程序流程的責(zé)任鏈設(shè)計(jì)模式(下)
大家好 我是寸鐵👊
【Gin】深度解析:在Gin框架中優(yōu)化應(yīng)用程序流程的責(zé)任鏈設(shè)計(jì)模式(下)?
喜歡的小伙伴可以點(diǎn)點(diǎn)關(guān)注 💝
前言
本次文章分為上下兩部分,上部分為對(duì)理論的介紹,下部分為具體的底層代碼深度剖析和編程實(shí)踐,感興趣的伙伴不要錯(cuò)過(guò)哦~
責(zé)任鏈設(shè)計(jì)模式作為一種經(jīng)典的行為設(shè)計(jì)模式,在現(xiàn)代軟件開(kāi)發(fā)中扮演著重要角色。特別是在高效的Web應(yīng)用開(kāi)發(fā)中,如Gin框架這樣的輕量級(jí)Go語(yǔ)言Web框架,合理地應(yīng)用責(zé)任鏈模式可以顯著提升代碼的可擴(kuò)展性和靈活性。本文將深入探討在Gin框架中責(zé)任鏈模式的實(shí)現(xiàn)原理、優(yōu)化策略以及實(shí)際應(yīng)用場(chǎng)景。
責(zé)任鏈模式通過(guò)將請(qǐng)求的發(fā)送者和接收者解耦,將多個(gè)對(duì)象連成一條鏈,并在鏈上傳遞請(qǐng)求,直到有對(duì)象處理該請(qǐng)求為止。在Gin
框架中,利用責(zé)任鏈模式可以有效地處理請(qǐng)求的流程控制、中間件管理和異常處理,使得代碼結(jié)構(gòu)更加清晰和可維護(hù)。本文將探索如何在Gin框架中設(shè)計(jì)和優(yōu)化責(zé)任鏈模式,以提升應(yīng)用程序的性能和可維護(hù)性。
關(guān)鍵的類圖和時(shí)序圖
(1)類圖
責(zé)任鏈模式包含以下幾個(gè)主要角色:
Handler
(抽象處理者):
聲明一個(gè)處理請(qǐng)求的接口,通常包含一個(gè)指向下一個(gè)處理者的引用。
MiddlerWare
(具體處理者):
Gin的中間件實(shí)現(xiàn)抽象處理者的處理方法,并根據(jù)自身的處理能力決定是否處理請(qǐng)求,如果不能處理則將請(qǐng)求傳遞給下一個(gè)處理者。
Client
(客戶端):
創(chuàng)建一個(gè)具體處理者對(duì)象的鏈,并向鏈上的第一個(gè)處理者對(duì)象發(fā)送請(qǐng)求。
圖 28 責(zé)任鏈模式類圖
由類圖28可得:先聲明一個(gè)處理請(qǐng)求的接口
Handler
,通常聚合一個(gè)指向下一個(gè)處理者的引用successor
。聲明處理請(qǐng)求的方法handlerRequest()
,由具體的中間件責(zé)任方MiddleWare
實(shí)現(xiàn)。接著Gin的中間件實(shí)現(xiàn)抽象處理者的處理方法,調(diào)用handlerRequest()
方法根據(jù)自身的處理能力決定是否處理請(qǐng)求,如果不能處理則將請(qǐng)求傳遞給下一個(gè)處理者nextHandler
??蛻舳藙?chuàng)建一個(gè)具體處理者對(duì)象的鏈,并向鏈上的第一個(gè)處理者對(duì)象發(fā)送請(qǐng)求。
(2)時(shí)序圖
圖 29 責(zé)任鏈模式時(shí)序圖
由上圖29可得:客戶端發(fā)送請(qǐng)求:客戶端調(diào)用具體處理者鏈的第一個(gè)處理者的
handleRequest()
方法,將請(qǐng)求request
傳遞給鏈的起始點(diǎn)MiddleWare1
。
責(zé)任鏈中的處理者Midlleware
處理請(qǐng)求:每個(gè)具體中間件處理者Midlleware
收到請(qǐng)求后,根據(jù)自己的處理能力決定是否處理請(qǐng)求。
請(qǐng)求傳遞到合適的處理者:如果當(dāng)前處理者能夠處理請(qǐng)求,則處理完成;如果不能處理,則將請(qǐng)求傳遞給鏈中的下一個(gè)處理者Midlleware
。
鏈的末端處理:請(qǐng)求request
會(huì)沿著鏈依次傳遞,直到被處理或者到達(dá)鏈的末端。如果末端處理者能夠處理請(qǐng)求processing Request,則處理完成后,沿著責(zé)任鏈返回處理完后的響應(yīng)信息
response`。如果整個(gè)鏈上的處理者都不能處理請(qǐng)求,則請(qǐng)求最終未被處理。
主程序的流程
由下圖30可知:一開(kāi)始,
Client
客戶端構(gòu)建責(zé)任鏈后,開(kāi)始發(fā)起請(qǐng)求,責(zé)任鏈的中間件MiddleWare1
開(kāi)始接收請(qǐng)求,然后處理請(qǐng)求,處理完畢后,返回響應(yīng)給上層調(diào)用者,如果客戶端接收到響應(yīng)后,則程序退出。
接著如果責(zé)任鏈的中間件MiddleWare1
無(wú)法處理請(qǐng)求,則將請(qǐng)求傳遞給下一層中間件MiddleWare2
,MiddleWare2
處理請(qǐng)求,處理完畢后,返回響應(yīng)給上層調(diào)用者,再由上層調(diào)用者沿責(zé)任鏈繼續(xù)將響應(yīng)信息返回給上層調(diào)用者,直至Client
客戶端。
如果責(zé)任鏈的中間件MiddleWare2
無(wú)法處理請(qǐng)求,則將請(qǐng)求傳遞給下一層中間件MiddleWare……
,MiddleWare……
處理請(qǐng)求,處理完畢后,返回響應(yīng)給上層調(diào)用者,再由上層調(diào)用者沿責(zé)任鏈繼續(xù)將響應(yīng)信息返回給上層調(diào)用者,直至Client
客戶端。
如果責(zé)任鏈的中間件都無(wú)法處理請(qǐng)求,則將請(qǐng)求傳遞給最后一層中間件FinalMiddleWare
處理請(qǐng)求,處理完畢后,返回響應(yīng)給上層調(diào)用者,再由上層調(diào)用者沿責(zé)任鏈繼續(xù)將響應(yīng)信息返回給上層調(diào)用者,直至Client
客戶端。如果最后一層中間件FinalMiddleWare
都無(wú)法處理請(qǐng)求,則責(zé)任鏈程序結(jié)束退出。
圖 30 責(zé)任鏈模式主程序流程圖
程序模塊之間的調(diào)用關(guān)系
下圖為程序各模塊之間的調(diào)用關(guān)系:
圖 31 責(zé)任鏈模式程序模塊調(diào)用圖
由上圖31可知,責(zé)任鏈模式主要有如下3個(gè)角色:
(1) IRoutes
(處理者):
定義處理請(qǐng)求的接口,通常包含一個(gè)處理方法(handle
),其具體實(shí)現(xiàn)方(ConcreteHandler
)實(shí)現(xiàn)處理請(qǐng)求的方式。每個(gè)處理者對(duì)象中通常會(huì)包含一個(gè)指向下一個(gè)處理者的引用(后繼者)。
(2) CombineHandlers
(具體處理者):
實(shí)現(xiàn) IRoutes
接口,處理請(qǐng)求的具體邏輯。如果自己能夠處理請(qǐng)求,則處理;否則將請(qǐng)求傳遞給下一個(gè)處理者。當(dāng)然,也可以設(shè)置攔截請(qǐng)求,將請(qǐng)求只在本層進(jìn)行處理,不傳遞給下一層責(zé)任方進(jìn)行處理。
(3) Client
(客戶端):
負(fù)責(zé)創(chuàng)建和提交請(qǐng)求對(duì)象。按照指定的順序構(gòu)建責(zé)任鏈,可以設(shè)置攔截器,用于攔截請(qǐng)求,最后將請(qǐng)求發(fā)送到鏈的起始點(diǎn)。
下面是對(duì)上圖各層次調(diào)用關(guān)系的描述:
客戶端調(diào)用
r.GET
進(jìn)行按照指定的順序構(gòu)建責(zé)任鏈,GET
方法實(shí)現(xiàn)IRoutes
接口,GET
方法將構(gòu)建路由組對(duì)象的請(qǐng)求轉(zhuǎn)發(fā)給真正的構(gòu)建路由組對(duì)象的handle方法,該handle
方法實(shí)現(xiàn)IRoutes
接口。handle方法進(jìn)一步轉(zhuǎn)發(fā)構(gòu)建GET
中指定順序的中間件責(zé)任鏈請(qǐng)求給具體處理者CombineHandlers
,具體處理者CombineHandlers
負(fù)責(zé)按照客戶端指定的順序構(gòu)建中間件責(zé)任鏈。責(zé)任鏈中的每一個(gè)處理者存在Next()
和Abort()
方法,調(diào)用Next()
方法遍歷責(zé)任鏈中的請(qǐng)求者對(duì)象,依次將請(qǐng)求傳遞給鏈中的具體處理者對(duì)象業(yè)務(wù)圖見(jiàn)下圖32。Abort()
方法設(shè)置索引為超出合法范圍的值,使得不將請(qǐng)求轉(zhuǎn)發(fā)給下一個(gè)處理者,實(shí)現(xiàn)攔截效果,業(yè)務(wù)圖見(jiàn)下圖33。構(gòu)建完后返回給上層的調(diào)用者Handler
,Handler
將路由組對(duì)象組裝好后,再將組裝好的路由組對(duì)象返回給GET
方法,GET
方法返回嵌入責(zé)任處理鏈的路由組對(duì)象給客戶端(責(zé)任鏈的起始點(diǎn))進(jìn)行調(diào)用,客戶端拿到對(duì)象后,做下一步的處理。
圖 32 責(zé)任鏈Next處理業(yè)務(wù)圖
圖 33 責(zé)任鏈Abort攔截業(yè)務(wù)圖
在上圖的基礎(chǔ)上,下面對(duì)各個(gè)模塊的代碼進(jìn)行深入剖析:
圖 34 責(zé)任鏈客戶端代碼
gin.Default()
創(chuàng)建了一個(gè)默認(rèn)的 Gin 路由引擎實(shí)例 r。
r.GET("", middleWare1, middleWare2, Home)
定義了一個(gè) GET 請(qǐng)求的路由,其中middleWare1
和middleWare2
是中間件函數(shù),用于在請(qǐng)求到達(dá)最終處理函數(shù)Home
之前執(zhí)行預(yù)處理或者其他操作。
r.Run(":8080")
啟動(dòng)了 HTTP 服務(wù)器,監(jiān)聽(tīng)在 8080 端口上。
圖 35 客戶端指定處理請(qǐng)求的中間件代碼
函數(shù)簽名和參數(shù):
func middleWare1(c *gin.Context)
:這是一個(gè)函數(shù)簽名,接收一個(gè)*gin.Context
類型的參數(shù)c
,用于處理 HTTP 請(qǐng)求和響應(yīng)。
處理邏輯:fmt.Println("M1 請(qǐng)求部分")
:這行代碼輸出 “M1 請(qǐng)求部分”,表示這是中間件處理請(qǐng)求的部分,可以在這里執(zhí)行一些預(yù)處理邏輯,如日志記錄、權(quán)限檢查等。
c.Next()
c.Next()
是 Gin 框架中用于將控制傳遞給鏈中的下一個(gè)處理程序的方法。在這里,它表示將控制權(quán)傳遞給下一個(gè)注冊(cè)的中間件或路由處理函數(shù)。fmt.Println(“M1 響應(yīng)部分”):這行代碼輸出 “M1 響應(yīng)部分”,表示中間件處理請(qǐng)求后的響應(yīng)部分,可以在這里執(zhí)行一些后續(xù)處理邏輯,如記錄響應(yīng)時(shí)間、清理資源等。
注釋的代碼c.Abort()
:
//c.Abort():這是一個(gè)被注釋掉的代碼片段。在 Gin 框架中,如果調(diào)用 c.Abort(),它將會(huì)中止當(dāng)前請(qǐng)求鏈的執(zhí)行,不會(huì)再繼續(xù)執(zhí)行后續(xù)的中間件或路由處理函數(shù)。如果取消注釋,將會(huì)導(dǎo)致請(qǐng)求處理過(guò)程被中止,不再執(zhí)行后續(xù)的處理函數(shù)。
執(zhí)行流程:
當(dāng)有一個(gè)
HTTP
請(qǐng)求到達(dá)與該中間件關(guān)聯(lián)的路由時(shí),這段代碼的執(zhí)行流程如下:當(dāng)請(qǐng)求進(jìn)入時(shí),中間件輸出 “M1 請(qǐng)求部分”,執(zhí)行一些請(qǐng)求前的邏輯。
c.Next()
調(diào)用將控制權(quán)傳遞給下一個(gè)中間件或路由處理函數(shù)。如果注釋掉的c.Abort()
被取消注釋,則請(qǐng)求的處理將在此中間件結(jié)束,不再繼續(xù)向下執(zhí)行。
如果沒(méi)有調(diào)用c.Abort()
或者注釋掉了,請(qǐng)求將繼續(xù)執(zhí)行下一個(gè)中間件或者最終的路由處理函數(shù)。當(dāng)控制返回給該中間件時(shí)(表示后續(xù)處理完成或者中間件鏈中斷),輸出 “M1 響應(yīng)部分”,執(zhí)行一些請(qǐng)求后的邏輯。
圖 36 IRoutes接口
代碼位置:RouterGroup.go的33-51行
方法解析:
Use
Use(...HandlerFunc) IRoutes
作用:注冊(cè)一個(gè)或多個(gè)中間件函數(shù),這些中間件函數(shù)會(huì)在后續(xù)的路由處理中被調(diào)用。返回值:返回 IRoutes 接口本身,以支持鏈?zhǔn)秸{(diào)用。
HTTP 方法相關(guān)路由
這些方法 (Handle, Any, GET, POST, DELETE, PATCH, PUT, OPTIONS, HEAD
) 都接受路由路徑作為第一個(gè)參數(shù),后跟一個(gè)或多個(gè) HandlerFunc,表示路由處理函數(shù)。每個(gè)方法都允許注冊(cè)對(duì)應(yīng) HTTP 方法的路由處理器。
返回值:同樣返回 IRoutes 接口,支持鏈?zhǔn)秸{(diào)用,以便在代碼中可以連續(xù)調(diào)用多個(gè)路由注冊(cè)方法。
Match
Match([]string, string, ...HandlerFunc) IRoutes
參數(shù):第一個(gè)參數(shù)是 HTTP 方法的列表,第二個(gè)參數(shù)是路由路徑模式,后跟一個(gè)或多個(gè)HandlerFunc
。
作用:注冊(cè)一個(gè)支持多種 HTTP 方法的路由處理器。
返回值:返回IRoutes
接口。
靜態(tài)文件服務(wù)相關(guān)方法:
StaticFile(string, string) IRoutes
:注冊(cè)單個(gè)靜態(tài)文件的路由處理器。
StaticFileFS(string, string, http.FileSystem) IRoutes
:注冊(cè)單個(gè)靜態(tài)文件的路由處理器,并指定文件系統(tǒng)。
Static(string, string) IRoutes
:注冊(cè)指定路徑下所有文件的靜態(tài)文件服務(wù)。
StaticFS(string, http.FileSystem) IRoutes
:注冊(cè)指定路徑下所有文件的靜態(tài)文件服務(wù),并指定文件系統(tǒng)。
總結(jié):
這個(gè)接口定義了一組方法,用于在一個(gè)路由處理器中注冊(cè)路由和處理函數(shù)。通過(guò)IRoutes
接口,可以方便地添加中間件、處理各種HTTP
方法的請(qǐng)求,以及處理靜態(tài)文件服務(wù)。這種設(shè)計(jì)使得路由的注冊(cè)和處理能夠保持清晰和模塊化,符合常見(jiàn)的 Web 框架的路由管理模式。
圖 37 GET方法代碼
代碼位置:routergroup.go的116-118行
函數(shù)簽名:
GET(relativePath string, handlers ...HandlerFunc)
:這是一個(gè)方法定義,屬于RouterGroup
類型的接收者group
。它接收一個(gè)相對(duì)路徑relativePath
和一個(gè)或多個(gè)HandlerFunc
類型的處理函數(shù)作為參數(shù)。
IRoutes
是一個(gè)接口,用于表示路由集合或路由的操作。
功能說(shuō)明:
GET
方法作為RouterGroup
的一個(gè)方法,是為了注冊(cè)一個(gè)處理GET
請(qǐng)求的路由。
relativePath string
參數(shù)表示注冊(cè)的路由的相對(duì)路徑,如 “/”、“/users” 等。
handlers ...HandlerFunc
參數(shù)是一個(gè)變長(zhǎng)參數(shù),接收一個(gè)或多個(gè) HandlerFunc 函數(shù),用來(lái)處理請(qǐng)求。
方法調(diào)用:
roup.handle(http.MethodGet, relativePath, handlers)
:在 GET 方法內(nèi)部,調(diào)用了group.handle
方法,將 HTTP 方法名 “GET”、相對(duì)路徑relativePath
和handlers
函數(shù)傳遞給它。
http.MethodGet
是 Go 標(biāo)準(zhǔn)庫(kù)中定義的常量,表示 HTTP GET 請(qǐng)求方法。
返回值:
return group.handle(...)
:GET 方法返回了group.handle(...)
的結(jié)果。通常情況下,這個(gè)方法會(huì)返回路由集合或者支持路由操作的接口,以便可以進(jìn)一步鏈?zhǔn)秸{(diào)用其他路由相關(guān)的方法。
執(zhí)行流程:
在 Gin 框架中,RouterGroup
類型的GET
方法是一個(gè)便捷方法,它內(nèi)部調(diào)用了group.handle
方法來(lái)處理注冊(cè) GET 請(qǐng)求的路由。具體的處理流程如下:當(dāng)調(diào)用 GET 方法注冊(cè)路由時(shí),實(shí)際上是通過(guò) group.handle 方法進(jìn)行注冊(cè)。group.handle 方法會(huì)將 “GET”、relativePath
和handlers
作為參數(shù)傳遞給底層的路由處理器進(jìn)行處理和注冊(cè)。這樣做的好處是可以通過(guò)不同的HTTP
方法(例如GET
,POST
,PUT
等)來(lái)注冊(cè)不同的路由處理邏輯,同時(shí)保持了代碼的簡(jiǎn)潔性和可讀性。
圖 38 具體處理者代碼
代碼位置:routergroup.go的86-91行
函數(shù)簽名:
handle(httpMethod, relativePath string, handlers HandlersChain)
:這是一個(gè)方法定義,屬于 RouterGroup 類型的接收者 group。它接收三個(gè)參數(shù):
httpMethod
:表示 HTTP 請(qǐng)求方法,如"GET"
、"POST"
等。
relativePath
:表示路由的相對(duì)路徑,例如 “/”、“/users” 等。
handlers HandlersChain
:是一個(gè)類型為 HandlersChain 的參數(shù),表示一系列的處理函數(shù)鏈。
路徑計(jì)算和處理器組合:
calculateAbsolutePath
方法確保生成正確的完整路徑,考慮了路由組的前綴等因素。combineHandlers
方法可能用來(lái)將當(dāng)前路由組的中間件與傳入的處理函數(shù)鏈合并,確保請(qǐng)求能夠按照正確的順序執(zhí)行。
路由注冊(cè):
addRoute
方法將最終確定的HTTP
方法、路徑和處理函數(shù)鏈注冊(cè)到Gin
框架的路由引擎中,以便后續(xù)能夠根據(jù)請(qǐng)求的HTTP
方法和路徑找到對(duì)應(yīng)的處理函數(shù)。
返回值:
returnObj
方法返回當(dāng)前路由組的某個(gè)接口或?qū)ο?#xff0c;用于可能的鏈?zhǔn)秸{(diào)用或其他路由相關(guān)操作。
這段代碼展示了 Gin 框架中路由注冊(cè)的核心邏輯。它負(fù)責(zé)計(jì)算路由的絕對(duì)路徑,合并處理函數(shù)鏈,最終將路由信息注冊(cè)到底層的路由引擎中。通過(guò)這種設(shè)計(jì),框架能夠支持靈活的路由定義和中間件處理,同時(shí)保證了性能和可擴(kuò)展性。
圖 39 具體處理者真正構(gòu)建責(zé)任鏈代碼
代碼位置:routergroup.go的241-248行
函數(shù)簽名:
combineHandlers(handlers HandlersChain) HandlersChain
:這是一個(gè)方法定義,屬于RouterGroup
類型的接收者group
。它接收一個(gè)類型為HandlersChain
的參數(shù) handlers,表示一系列的處理函數(shù)鏈。返回類型為HandlersChain
,即處理函數(shù)鏈。
參數(shù)解釋:
handlers HandlersChain
:表示要合并到當(dāng)前路由組 (group) 的處理函數(shù)鏈。HandlersChain
可能是一個(gè)類型為[]func(c *Context)
的切片,用于存儲(chǔ)中間件和處理函數(shù)。
計(jì)算最終大小:
finalSize := len(group.Handlers) + len(handlers)
:計(jì)算合并后的處理函數(shù)鏈的總長(zhǎng)度。group.Handlers
是當(dāng)前路由組已有的處理函數(shù)鏈的長(zhǎng)度,handlers
是傳入的新的處理函數(shù)鏈的長(zhǎng)度。
斷言檢查:
assert1(finalSize < int(abortIndex), "too many handlers")
:使用 assert1 函數(shù)來(lái)斷言finalSize
必須小于abortIndex
,否則會(huì)輸出 “too many handlers” 的錯(cuò)誤信息。這是為了確保合并后的處理函數(shù)鏈不會(huì)超過(guò)某個(gè)預(yù)設(shè)的最大限制,避免潛在的內(nèi)存溢出或其他問(wèn)題。
創(chuàng)建合并后的處理函數(shù)鏈:
mergedHandlers := make(HandlersChain, finalSize)
:根據(jù) finalSize 創(chuàng)建一個(gè)新的HandlersChain
,即mergedHandlers
,用于存儲(chǔ)合并后的處理函數(shù)鏈。
復(fù)制處理函數(shù):
copy(mergedHandlers, group.Handlers)
:將當(dāng)前路由組 (group) 的已有處理函數(shù)鏈復(fù)制到mergedHandlers ``的開(kāi)頭部分。
copy(mergedHandlers[len(group.Handlers):], handlers):將傳入的新處理函數(shù)鏈 handlers 復(fù)制到 mergedHandlers 的末尾部分,從 group.Handlers 的長(zhǎng)度位置開(kāi)始復(fù)制。
返回合并后的處理函數(shù)鏈:
return mergedHandlers
:返回合并后的 HandlersChain,即包含了當(dāng)前路由組的處理函數(shù)鏈和傳入的新處理函數(shù)鏈的完整鏈條。
處理函數(shù)鏈合并:
combineHandlers
方法用于將當(dāng)前路由組的已有處理函數(shù)鏈與傳入的新處理函數(shù)鏈進(jìn)行合并,確保請(qǐng)求按照正確的順序執(zhí)行所有中間件和處理函數(shù)。
長(zhǎng)度和斷言檢查:
在合并前,通過(guò)計(jì)算和斷言來(lái)確保合并后的處理函數(shù)鏈不會(huì)過(guò)長(zhǎng),以保證系統(tǒng)的穩(wěn)定性和性能。
返回值:
返回合并后的處理函數(shù)鏈,以便在路由注冊(cè)時(shí)使用。
這段代碼展示了 Gin 框架中如何處理路由組的處理函數(shù)鏈的合并邏輯。通過(guò)這種方式,框架能夠支持在路由組中動(dòng)態(tài)添加中間件和處理函數(shù),保證了靈活性和可擴(kuò)展性。
圖 40 責(zé)任鏈的數(shù)據(jù)結(jié)構(gòu)
代碼位置:gin.go的54行
定義:定義了一個(gè)
HandlerFunc
請(qǐng)求處理者的切片,用于存儲(chǔ)HandlerFunc
請(qǐng)求處理者,構(gòu)建責(zé)任鏈。
圖 41 請(qǐng)求處理者的定義
代碼位置:gin.go的48行
定義:定義
HandlerFunc
類型,用于創(chuàng)建具體請(qǐng)求處理對(duì)象。
圖 42 責(zé)任鏈訪問(wèn)下一個(gè)責(zé)任方Next()代碼
代碼位置:context.go的182-188行
方法說(shuō)明:
c.index
是 Context 結(jié)構(gòu)體中的一個(gè)字段,用于跟蹤當(dāng)前執(zhí)行的中間件或處理函數(shù)的位置。
c.handlers
是一個(gè)存儲(chǔ)HandlerFunc
的切片,這些函數(shù)是注冊(cè)到當(dāng)前路由處理器的中間件和處理函數(shù)。
c.index++
將 index 遞增,以準(zhǔn)備執(zhí)行下一個(gè)中間件或處理函數(shù)。
for
循環(huán)用來(lái)遍歷 handlers 中的函數(shù),從 index 所指的位置開(kāi)始執(zhí)行,直到數(shù)組末尾或者中途某個(gè)函數(shù)決定中斷執(zhí)行。
執(zhí)行流程:
調(diào)用Next()
方法會(huì)使 index 遞增,從而將控制權(quán)交給下一個(gè)注冊(cè)的中間件或處理函數(shù)。
每次循環(huán),通過(guò)c.handlers[c.index](c)
調(diào)用 index 所指的函數(shù),并將當(dāng)前的Context
對(duì)象c
傳遞給它。
循環(huán)繼續(xù),直到index
超過(guò)了handlers
的長(zhǎng)度或者某個(gè)中間件函數(shù)調(diào)用了Next()
以停止后續(xù)執(zhí)行。
圖43 定義攔截索引
代碼位置:context.go的50-51行
abortIndex
是一個(gè)int8
類型的常量。
math.MaxInt8
是int8
類型能表示的最大整數(shù),通常為127
。
>> 1
是位運(yùn)算操作,表示將 math.MaxInt8 右移一位,即將其值除以 2,得到的結(jié)果約為 63(實(shí)際值取決于具體的整數(shù)大小和運(yùn)算系統(tǒng))。
這樣設(shè)定的目的是使abortIndex
成為一個(gè)比較大的數(shù)值,足以確保c.index
大于等于abortIndex
后可以立即停止后續(xù)的處理函數(shù)調(diào)用。
圖 44 請(qǐng)求處理者設(shè)置攔截器
代碼位置:context.go的199-201行
代碼解釋:
c.index
是 Context
結(jié)構(gòu)體中的一個(gè)字段,用于跟蹤當(dāng)前執(zhí)行的中間件或處理函數(shù)的位置。
abortIndex
是一個(gè)常量或全局變量,用于表示中止處理流程的索引值。
Abort()
方法將 c.index
設(shè)置為 abortIndex
,這樣在接下來(lái)調(diào)用 Next()
方法時(shí),循環(huán)將直接結(jié)束,不再執(zhí)行后續(xù)的處理函數(shù)或中間件。
圖 45 判斷請(qǐng)求處理者是否設(shè)置攔截
代碼位置:context.go的191-193行
代碼解釋:
c.index
是Context
結(jié)構(gòu)體中的一個(gè)字段,用于跟蹤當(dāng)前執(zhí)行的中間件或處理函數(shù)的位置。
abortIndex
是一個(gè)常量或全局變量,用于表示中止(abort
)處理流程的索引值。
IsAborted()
方法通過(guò)比較c.index
是否大于或等于abortIndex
來(lái)判斷當(dāng)前處理流程是否已經(jīng)被中止。
如果c.index
大于或等于abortIndex
,則返回true
,表示當(dāng)前處理已被中止;否則返回false
,表示未被中止。
責(zé)任鏈模式案例及調(diào)試分析
責(zé)任鏈模式案例編寫(xiě)如下:
下面分析一下每個(gè)部分的功能和調(diào)用流程:
結(jié)構(gòu)定義和接口:
圖101 定義Handler接口
Handler
接口:
Handle(c *gin.Context)
方法用于處理請(qǐng)求。
SetNext(handler Handler)
方法用于設(shè)置下一個(gè)責(zé)任鏈節(jié)點(diǎn)。
圖102 定義Middleware中間件
Middleware
結(jié)構(gòu)體:實(shí)現(xiàn)了 Handler 接口。
圖103 定義中間件1的Handle方法
Handle(c *gin.Context)
方法中,打印 “M1 接收請(qǐng)求”,然后調(diào)用下一個(gè)處理者(如果存在),最后打印 “M1 得到響應(yīng)”。
圖104 定義中間件2的Handle方法
Handle(c *gin.Context)
方法中,打印 “M2 接收請(qǐng)求”,然后調(diào)用下一個(gè)處理者(如果存在),最后打印 “M2 得到響應(yīng)”。
圖105 定義末端中間件的Handle方法
FinalHandler
結(jié)構(gòu)體:作為最終的處理者,實(shí)現(xiàn)了 Handler 接口。
Handle(c *gin.Context)
方法中,打印 “FinalHandler 接收請(qǐng)求”,然后調(diào)用具體的業(yè)務(wù)邏輯函數(shù) Home(c)
,最后打印 “FinalHandler 得到響應(yīng)”。
圖106 定義視圖層代碼
Home
視圖層部分:
fmt.Println("Home Receiving……")
放在 Home 函數(shù)中的最開(kāi)始,這樣在請(qǐng)求到達(dá)時(shí)會(huì)立即打印 “Home Receiving……”。
c.String(200, "Home Receiving……")
在完成日志記錄后立即向客戶端發(fā)送 “Home Receiving……” 響應(yīng)
責(zé)任鏈的構(gòu)建和運(yùn)行:
圖107 客戶端構(gòu)建責(zé)任鏈
解讀: 在
main
函數(shù)中:先創(chuàng)建了Gin
引擎實(shí)例r
。再實(shí)例化了Middleware1
、Middleware2
和FinalHandler
。之后使用SetNext
方法將它們串聯(lián)起來(lái),形成責(zé)任鏈:middleware1 -> middleware2 -> finalHandler。將middleware1.Handle
方法作為 Gin 路由處理函數(shù)注冊(cè)到了根路徑 “”,這意味著當(dāng)收到 GET 請(qǐng)求時(shí),責(zé)任鏈會(huì)依次處理該請(qǐng)求。最后通過(guò)r.Run(":8080")
啟動(dòng) Gin 服務(wù)器,監(jiān)聽(tīng)在 8080 端口上。
小結(jié):責(zé)任鏈模式案例展示了如何使用 Go 和 Gin 框架構(gòu)建一個(gè)簡(jiǎn)單的責(zé)任鏈,用于處理 HTTP 請(qǐng)求。每個(gè)中間件和最終處理者都負(fù)責(zé)一部分邏輯,并通過(guò) SetNext 方法連接成鏈條,確保請(qǐng)求依次經(jīng)過(guò)每個(gè)處理者,并且每個(gè)處理者都能在適當(dāng)?shù)臅r(shí)機(jī)打印日志和處理響應(yīng)。
調(diào)試分析:
在執(zhí)行時(shí),假設(shè)收到一個(gè) GET 請(qǐng)求:
Gin 路由會(huì)將該請(qǐng)求交給middleware1.Handle
處理。
middleware1.Handle
中會(huì)打印 “M1 接收請(qǐng)求”,然后調(diào)用 middleware2.Handle。
middleware2.Handle
中會(huì)打印 “M2 接收請(qǐng)求”,然后調(diào)用 finalHandler.Handle。
finalHandler.Handle
中會(huì)打印 “FinalHandler 接收請(qǐng)求”,然后調(diào)用Home(c)
處理實(shí)際的業(yè)務(wù)邏輯。
Home(c)
會(huì)在控制臺(tái)打印 “Home Receiving……”,并向客戶端返回 “Home Receiving……” 字符串。
控制流會(huì)逆序返回,最終 finalHandler.Handle 打印 “FinalHandler 得到響應(yīng)”,然后依次是 middleware2.Handle 和 middleware1.Handle 的響應(yīng)打印。
Gin引擎對(duì)象
啟動(dòng)成功,代碼無(wú)報(bào)錯(cuò),責(zé)任鏈構(gòu)建成功,正在監(jiān)聽(tīng)8080
端口,Demo啟動(dòng)成功!具體輸出結(jié)果見(jiàn)測(cè)試結(jié)果部分。
圖 108 成功啟動(dòng)責(zé)任鏈模式案例
責(zé)任鏈模式測(cè)試結(jié)果
APIfox測(cè)試工具監(jiān)聽(tīng)向8080端口發(fā)送GET請(qǐng)求:顯示Home Reciving……
,說(shuō)明責(zé)任鏈構(gòu)建成功,且將信息Home Reciving……
正確顯示到客戶端。
圖132 Apifox發(fā)起GET測(cè)試請(qǐng)求
責(zé)任鏈模式測(cè)試結(jié)果進(jìn)一步剖析如下:
圖133 責(zé)任鏈模式測(cè)試結(jié)果剖析圖
客戶端構(gòu)建責(zé)任鏈并發(fā)起監(jiān)聽(tīng)8080端口請(qǐng)求,接下來(lái)分析控制臺(tái)輸出的順序是否與調(diào)試分析的預(yù)測(cè)一致:
圖134 Apifox發(fā)起GET請(qǐng)求
訪問(wèn) http://localhost:8080/
,向服務(wù)端發(fā)出一條GET請(qǐng)求,可以看到以下控制臺(tái)輸出:
(1) 請(qǐng)求發(fā)出:
M1
接收請(qǐng)求:Middleware1 接收請(qǐng)求。與分析圖的序號(hào)①對(duì)應(yīng)
M2
接收請(qǐng)求:Middleware2 接收請(qǐng)求。與分析圖的序號(hào)②對(duì)應(yīng)
FinalHandler
接收請(qǐng)求:FinalHandler 接收請(qǐng)求。與分析圖的序號(hào)③對(duì)應(yīng)
Home
視圖層 Home Receiving……:Home 函數(shù)處理請(qǐng)求,輸出 “Home Receiving……”。與分析圖的序號(hào)④對(duì)應(yīng)
同時(shí)向客戶端(APIfox發(fā)起的請(qǐng)求端)發(fā)送"Home Receiving……
(2) 接收響應(yīng):
FinalHandler
得到響應(yīng):FinalHandler 處理完請(qǐng)求,得到響應(yīng)。與分析圖的序號(hào)⑤對(duì)應(yīng)
M2
得到響應(yīng):Middleware2 得到最終處理結(jié)果的響應(yīng)。與分析圖的序號(hào)⑥對(duì)應(yīng)
M1
得到響應(yīng):Middleware1 得到 Middleware2 的響應(yīng),最終完成整個(gè)請(qǐng)求的處理。與分析圖的序號(hào)⑦對(duì)應(yīng)。
綜上,無(wú)論是控制臺(tái)輸出的結(jié)果還是客戶端顯示的信息,都與整個(gè)調(diào)試分析的結(jié)果一致,責(zé)任鏈構(gòu)建成功,測(cè)試通過(guò)!
圖135服務(wù)端監(jiān)聽(tīng)端口多次測(cè)試請(qǐng)求
持續(xù)發(fā)起多次請(qǐng)求,責(zé)任鏈也能夠正常處理,不發(fā)生請(qǐng)求,則持續(xù)監(jiān)聽(tīng)端口。
再測(cè)試一下攔截請(qǐng)求的效果,這里只需要將調(diào)用下一個(gè)處理者代碼替換為c.Abort()
即可,這樣就能將請(qǐng)求攔截在Middleware2
處理者,而
不會(huì)傳給下一層的具體處理者。
圖 136 進(jìn)行請(qǐng)求的攔截
預(yù)期結(jié)果如下:
由于在Middleware2
這一層攔截掉,請(qǐng)求不會(huì)轉(zhuǎn)發(fā)給下一層處理,即最后處理者和Home
業(yè)務(wù)函數(shù)的內(nèi)容都不會(huì)輸出,客戶端也不會(huì)顯示出Home Receiving……
輸出結(jié)果預(yù)期如下:
M1
接收請(qǐng)求M2
接收請(qǐng)求 M2 得到響應(yīng) M1 得到響應(yīng)
測(cè)試結(jié)果如下:
客戶端發(fā)起請(qǐng)求,沒(méi)有接收到Home發(fā)送的內(nèi)容,測(cè)試結(jié)果與預(yù)期一致!
圖137 客戶端發(fā)起GET請(qǐng)求
再看一下控制臺(tái)輸出的內(nèi)容:只輸出M1和M2兩個(gè)中間件有關(guān)的內(nèi)容,無(wú)輸出最后處理者的內(nèi)容,測(cè)試結(jié)果與預(yù)期一致!
圖138 服務(wù)端監(jiān)聽(tīng)端口輸出信息
結(jié)語(yǔ)
責(zé)任鏈模式作為一種優(yōu)秀的設(shè)計(jì)模式,在Gin框架中展現(xiàn)了其強(qiáng)大的靈活性和可擴(kuò)展性。通過(guò)本文的探討,我們深入理解了責(zé)任鏈模式在處理請(qǐng)求流程、中間件管理和異常處理方面的應(yīng)用。合理地利用責(zé)任鏈模式可以使代碼更加模塊化和可復(fù)用,從而提高了應(yīng)用程序的設(shè)計(jì)質(zhì)量和開(kāi)發(fā)效率。希望本文能夠?yàn)殚_(kāi)發(fā)者提供實(shí)用的指導(dǎo)和啟發(fā),幫助他們?cè)趯?shí)際項(xiàng)目中充分發(fā)揮責(zé)任鏈模式的優(yōu)勢(shì),構(gòu)建更加健壯和高效的Web應(yīng)用程序。
看到這里的小伙伴,恭喜你又掌握了一個(gè)技能👊
希望大家能取得勝利,堅(jiān)持就是勝利💪
我是寸鐵!我們下期再見(jiàn)💕
往期好文💕
保姆級(jí)教程
【保姆級(jí)教程】Windows11下go-zero的etcd安裝與初步使用
【保姆級(jí)教程】Windows11安裝go-zero代碼生成工具goctl、protoc、go-zero
【Go-Zero】手把手帶你在goland中創(chuàng)建api文件并設(shè)置高亮
報(bào)錯(cuò)解決
【Go-Zero】Error: user.api 27:9 syntax error: expected ‘:‘ | ‘IDENT‘ | ‘INT‘, got ‘(‘ 報(bào)錯(cuò)解決方案及api路由注意事項(xiàng)
【Go-Zero】Error: only one service expected goctl一鍵轉(zhuǎn)換生成rpc服務(wù)錯(cuò)誤解決方案
【Go-Zero】【error】 failed to initialize database, got error Error 1045 (28000):報(bào)錯(cuò)解決方案
【Go-Zero】Error 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using password: YES)報(bào)錯(cuò)解決方案
【Go-Zero】type mismatch for field “Auth.AccessSecret“, expect “string“, actual “number“報(bào)錯(cuò)解決方案
【Go-Zero】Error: user.api 30:2 syntax error: expected ‘)‘ | ‘KEY‘, got ‘IDENT‘報(bào)錯(cuò)解決方案
【Go-Zero】Windows啟動(dòng)rpc服務(wù)報(bào)錯(cuò)panic:context deadline exceeded解決方案
Go面試向
【Go面試向】defer與time.sleep初探
【Go面試向】defer與return的執(zhí)行順序初探
【Go面試向】Go程序的執(zhí)行順序
【Go面試向】rune和byte類型的認(rèn)識(shí)與使用
【Go面試向】實(shí)現(xiàn)map穩(wěn)定的有序遍歷的方式