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