網站首頁 編程語言 正文
一、前言 ??
Beego Web框架應該是國內Go語言社區第一個框架,個人覺得十分適合新手入門Go Web。筆者半年前寫過一篇搭建Beego項目并實習簡單功能的文章,大家有興趣可以先看看。
其實我接觸的大部分人都在學校學過Java Web,其實有Java Web的經驗,上手Beego也會很舒服。
本文著重講講Beego的AutoRouter模塊,會結合源碼來講講,不過由于筆者技術水平有限,如有錯誤,煩請指出。??
二、從一個例子入手?
Beego的路由設計靈感是sinatra,剛開始并不支持自動路由,項目的每一個路由都需要開發者配置。
?? 不過,在Beego里面注冊一個路由是十分簡單的,不信你看:
import "github.com/beego/beego/v2/server/web" type ReganYueController struct { web.Controller }
接下來我們可以添加一個方法,也可以重寫Get,Post,Delete等方法來響應客戶端不同的請求方式。
import "github.com/beego/beego/v2/server/web" type ReganYueController struct { web.Controller } func (u *ReganYueController) HelloWorld() { u.Ctx.WriteString("Welcome, Regan Yue") } func main() { web.AutoRouter(&ReganYueController{}) web.Run() }
該處web.AutoRouter(&ReganYueController{})
就是使用的自動路由,如果是以前的話,我們還需要配置路由?? 。例如以下這種形式:
beego.Router("/", &IndexController{})
對于下面這段代碼,有幾點需要注意:
func (u *ReganYueController) HelloWorld() { u.Ctx.WriteString("Welcome, Regan Yue") }
?這個處理HTTP請求的方法必須是公共方法(首字母要大寫),并且不能有參數,不能有返回值,若非如此,可能會發生Panic。
??AutoRouter的解析規則:
影響因素有三:
-
RouterCaseSensitive
的值。 -
Controller
的名字 - 方法名字
比如我們上面ReganYueController的名字是ReganYue,而方法名字是HelloWorld,那么就會有以下幾種情況出現:
- 如果
RouterCaseSensitive
為true
,那么AutoRouter就會注冊兩個路由,其中一個是/ReganYue/HelloWorld/*
,另一個是/reganyue/helloworld/*
。 - 如果
RouterCaseSensitive
為false
,那么AutoRouter只會注冊一個路由,即/reganyue/helloworld/*
。
三、AutoRouter是如何工作的
先看看web.AutoRouter()
// AutoRouter see HttpServer.AutoRouter func AutoRouter(c ControllerInterface) *HttpServer { return BeeApp.AutoRouter(c) }
web.AutoRouter()
馬上又指向(app *HttpServer) AutoRouter(c ControllerInterface)
// AutoRouter adds defined controller handler to BeeApp. // it's same to HttpServer.AutoRouter. // if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page, // visit the url /main/list to exec List function or /main/page to exec Page function. func (app *HttpServer) AutoRouter(c ControllerInterface) *HttpServer { app.Handlers.AddAuto(c) return app }
前面傳來的主語BeeApp
執行該處程序:
BeeApp是一個應用實例,使用NewHttpSever()創建,繼續跟進,發現是根據Bconfig
這個配置文件創建的,
// NewHttpServerWithCfg will create an sever with specific cfg func NewHttpServerWithCfg(cfg *Config) *HttpServer { cr := NewControllerRegisterWithCfg(cfg) app := &HttpServer{ Handlers: cr, Server: &http.Server{}, Cfg: cfg, } return app }
上圖即配置Bconfig的主要結構。
到此我們對于BeeApp已經有一定了解了,下面我們回過頭來看看app.Handlers.AddAuto(c)
。
先看看這個c
是什么,它的類型是ControllerInterface
,我們現在進去看看。
這個c是用來統一所有controller handler的接口。
根據上圖我們可以知道,這個app.Handles就是ControllerRegister,再來看看ControllerRegister的AddAuto方法:
func (p *ControllerRegister) AddAuto(c ControllerInterface) { p.AddAutoPrefix("/", c) }
AddAuto又指向AddAutoPrefix,這個AddAutoPrefix有什么用,我們先給出一個例子,然后再來看源碼。
beego.AddAutoPrefix("/admin",&MainContorlller{})
如果MainContorlller
有兩個方法List
、Page
。那么我們可以訪問/admin/main/list
來執行List
函數,訪問/admin/main/page
來執行Page
函數
來看看ControllerRegister的AddAutoPrefix方法:
func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface) { //對傳入的Controller做反射 reflectVal := reflect.ValueOf(c) //獲取傳入的Controller的類型 rt := reflectVal.Type() //因為c是指針,所以要用Indirect方法獲取指針指向的變量類型 ct := reflect.Indirect(reflectVal).Type() //使用Beego注冊controller的名稱后面有Controller,這里把它去掉得到controllerName。 controllerName := strings.TrimSuffix(ct.Name(), "Controller") // for i := 0; i < rt.NumMethod(); i++ { if !utils.InSlice(rt.Method(i).Name, exceptMethod) { route := &ControllerInfo{} route.routerType = routerTypeBeego route.methods = map[string]string{"*": rt.Method(i).Name} route.controllerType = ct pattern := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name), "*") patternInit := path.Join(prefix, controllerName, rt.Method(i).Name, "*") patternFix := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name)) patternFixInit := path.Join(prefix, controllerName, rt.Method(i).Name) route.pattern = pattern for m := range HTTPMETHOD { p.addToRouter(m, pattern, route) p.addToRouter(m, patternInit, route) p.addToRouter(m, patternFix, route) p.addToRouter(m, patternFixInit, route) } } } }
reflectVal.Type()
直接的獲取傳入的Controller的類型,而reflect.Indirect(reflectVal).Type()
,interface其實就是兩個指針,一個指向類型信息,一個指向實際的對象,用Indirect方法獲取指針指向的實際變量的類型。
在runtime/runtime2.go
可以了解interface其實就是兩個指針:
type iface struct { tab *itab //類型信息 data unsafe.Pointer //實際對象指針 } type itab struct { inter *interfacetype //接口類型 _type *_type //實際對象類型 hash uint32 _ [4]byte fun [1]uintptr //實際對象方法地址 }
接下來是for i := 0; i < rt.NumMethod(); i++
,我們來看看這個NumMethod()
,可以看到這個方法獲得interface類型的方法數量。
utils.InSlice()方法正如其名:
func InSlice(v string, sl []string) bool { for _, vv := range sl { if vv == v { return true } } return false }
該方法是用來判斷字符串v是不是在字符串切片sl里面。
此處判斷方法名是不是在exceptMethod里面。
下面是exceptMethod的內容:
exceptMethod = []string{"Init", "Prepare", "Finish", "Render", "RenderString", "RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJSON", "ServeJSONP", "ServeYAML", "ServeXML", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool", "GetFloat", "GetFile", "SaveToFile", "StartSession", "SetSession", "GetSession", "DelSession", "SessionRegenerateID", "DestroySession", "IsAjax", "GetSecureCookie", "SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml", "GetControllerAndAction", "ServeFormatted"}
接下來創建了一個結構體,記錄了controller的信息,下面幾行代碼就生成了每個方法對應的controller信息。
controller的pattern這里生成了4個模式:
- prefix/全小寫的controllerName/全小寫的方法名/*
- prefix/controllerName/方法名/*
- prefix/全小寫的controllerName/全小寫的方法名
- prefix/controllerName/方法名
然后對每一種HTTP方法:
都使用addToRouter
方法用四種模式執行一遍。
下面看看addToRouter。
func (p *ControllerRegister) addToRouter(method, pattern string, r *ControllerInfo) { if !p.cfg.RouterCaseSensitive { pattern = strings.ToLower(pattern) } if t, ok := p.routers[method]; ok { t.AddRouter(pattern, r) } else { t := NewTree() t.AddRouter(pattern, r) p.routers[method] = t } }
- 如果
RouterCaseSensitive
為true
,那么AutoRouter就會注冊兩個路由,其中一個是/ReganYue/HelloWorld/*
,另一個是/reganyue/helloworld/*
。 - 如果
RouterCaseSensitive
為false
,那么AutoRouter只會注冊一個路由,即/reganyue/helloworld/*
。
然后將method傳給ControllerRegister,看是不是注冊成功。
成功就執行:t.AddRouter(pattern, r)
添加路由。
否則就執行:
t := NewTree() t.AddRouter(pattern, r) p.routers[method] = t
那就到此為止吧,
再愛就不禮貌了...
結語
原文鏈接:https://juejin.cn/post/7133871580430401544
相關推薦
- 2023-01-10 Go語言rune與字符串轉換的密切關系解析_Golang
- 2023-03-02 Kotlin關于協程是什么的探究_Android
- 2022-07-04 Python基礎之矩陣輸入的實例_python
- 2023-01-04 Opencv實現鼠標事件與窗口互動功能過程_python
- 2023-04-10 Android序列化接口Parcelable與Serializable接口對比_Android
- 2022-02-11 為了兼容IE,配置Babel+Webpack
- 2022-09-07 解析react?函數組件輸入卡頓問題?usecallback?react.memo_React
- 2023-01-30 python第三方異步日志庫loguru簡介_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支