主要內(nèi)容:控制器解析需曾、handler注冊涩禀。
調(diào)用方法:
func (app *Application) Handle(controller interface{}) *Application {
c := newControllerActivator(app.Router, controller, app.Dependencies)
if before, ok := controller.(interface {
BeforeActivation(BeforeActivation)
}); ok {
before.BeforeActivation(c)
}
c.activate()
if after, okAfter := controller.(interface {
AfterActivation(AfterActivation)
}); okAfter {
after.AfterActivation(c)
}
return app
}
go語言接口查詢檢測自定義handler是否需要加載
對于JAVA惫撰,需要檢測控制器杆兵。一旦繼承了某個接口,就要實(shí)現(xiàn)其全部的接口方法,因而不需要檢測咆畏,直接調(diào)用重寫的方法南捂。
對于JAVA,go語言的的作用域更加開放旧找,所有的變量類型只區(qū)分包內(nèi)可見溺健,或是包外。用變量名的大小寫實(shí)現(xiàn)钦讳。對于接口也是不需要顯式指定實(shí)現(xiàn)是的哪些接口矿瘦,可以直接實(shí)現(xiàn)接口方法。更加靈活地繼承任何接口愿卒。這樣淡化了接口設(shè)計缚去,使類可以靈活實(shí)現(xiàn)各種接口。
由于自定義Handler需要重寫B(tài)eforeActivation方法琼开。用接口查詢的方式易结,檢測控制器實(shí)現(xiàn)了哪些接口,再調(diào)用方法柜候。對應(yīng)于JAVA顯式指明implements哪些接口搞动,調(diào)用用接口方法。
ControllerActivator結(jié)構(gòu)體
控制器BeoreActivation傳入的實(shí)例渣刷,是ControllerActivator{}對象鹦肿。實(shí)現(xiàn)了BeforeActivation接口和AfterActivation接口。這些接口形式如下:
type shared interface {
Name() string
Router() router.Party
GetRoute(methodName string) *router.Route
Handle(httpMethod, path, funcName string, middleware ...context.Handler) *router.Route
}
type BeforeActivation interface {
shared
Dependencies() *di.Values
}
type AfterActivation interface {
shared
DependenciesReadOnly() ValuesReadOnly
Singleton() bool
}
ControllerActivator數(shù)據(jù)結(jié)構(gòu)如下:
type ControllerActivator struct {
router router.Party
fullName string
routes map[string]*router.Route
dependencies di.Values
injector *di.StructInjector
}
router.Party接口辅柴。其實(shí)現(xiàn)類router.Router表示一組具有相同前綴和中間件的routes箩溃。router.Router是app(iris實(shí)例)的一個字段。最基本的功能:
- 調(diào)用Handle HandleMany方法注冊路由碌嘀。
- 調(diào)用Get/Post/Put/Delete/Head方法(根據(jù)不同的http請求類型)返回http請求Path對應(yīng)的路由*Route涣旨。
- 調(diào)用Use方法注冊中間件。
- 調(diào)用Done為路徑下的所有路由股冗,添加后處理handler霹陡。
- 其它的信息打印、生成子路由集合等止状。
所以router.Router就是iris的路由管理器烹棉。再用routes map[string]*router.Route保存一份此控制器的路由。fullName字段保存控制器名稱怯疤,形式user/controller.g峦耘,則為user.Controller。以下3字段用來反射調(diào)用控制器:
Value reflect.Value
Type reflect.Type
dependencies di.Values //di.Values是[]reflect.Value的別名
injector di.StructInjector字段旅薄,用于注入一個di.StructInjector實(shí)例。
ControllerActivator的構(gòu)建
ControllerActivator的功能是反射獲取控制器obj,構(gòu)建路由并保存到router.Router少梁。
ControllerActivator的構(gòu)建洛口,傳入了app.Router app.Dependencies及控制器obj。其中router字段直接由app拿到凯沪。Type Value由控制器反射拿到第焰。
dependencies字段包括了app本身的dependencies,以及controller中的字段妨马。注意到參數(shù)并不是引用傳遞挺举,而是深拷貝的。本例中controller沒有字段烘跺。
routes的數(shù)據(jù)結(jié)構(gòu)為map[string]*router.Route湘纵,key為控制器中的方法名稱。本身會預(yù)加載BeforeActivation滤淳,AfterActivation兩個方法梧喷,值為空,作為保留方法脖咐。本例中使用了用BeforeActivation來加載自定義的handler铺敌。如果實(shí)現(xiàn)了BaseController,還應(yīng)將BeginRequest屁擅,EndRequest作為保留方法偿凭。后續(xù)的handleRequest()方法在解析控制器中的方法,將保存到路由時派歌,會首先校驗(yàn)方法弯囊,是不是保留方法,如果是則直接返回硝皂,不進(jìn)行后續(xù)的解析和構(gòu)建常挚、創(chuàng)建路由操作。
“手動”和“自動”化地調(diào)用Handle注冊路由
由于iris使用了NamingGuide來寫處理http請求的方法稽物。不需要每次都寫RequestMethod RequestMapping奄毡。標(biāo)準(zhǔn)化了這些方法的函數(shù)名,含義清晰贝或『鸸框架以“自動”化地方工處理這些函數(shù)。而時又支持手動添加自定義的Handle()咪奖。
Handle方法API如下:
Handle(httpMethod, path, funcName string, middleware ...context.Handler) *router.Route
只需要指明http請求方法盗忱、http請求路徑、funcName羊赵、Handler趟佃。從funcName和controller實(shí)體扇谣,即可以反射調(diào)用對應(yīng)的方法。handler的實(shí)現(xiàn)形式非常簡單闲昭,只有一個傳入?yún)?shù)ctx context罐寨,包括了請求信息、響應(yīng)及其它上下文信息序矩。
自定義的方法鸯绿,會在BeforeActivation中直接調(diào)用Handle中注冊路由。
而對于NamingGuide的常規(guī)方法簸淀,會調(diào)用反射遍歷控制器中的所有方法瓶蝴。如上面所描述,先依據(jù)c.routes檢查是否是保護(hù)函數(shù)名租幕。有一個編程細(xì)節(jié)是舷手,保留字判斷方法c.isReservedMethod,使用了閉包傳遞令蛉。
反射獲取m.Name之后調(diào)用解析方法:
(p *methodParser) parse() (method, path string, err error)
其中methodParser:
type methodParser struct {
lexer *methodLexer
fn reflect.Method
}
methodLexer是有狀態(tài)的數(shù)組聚霜,由于go標(biāo)準(zhǔn)庫,只有container/list的鏈表實(shí)現(xiàn)珠叔,沒有ArrayList的實(shí)現(xiàn)蝎宇,并且沒有iterator。此數(shù)據(jù)結(jié)構(gòu)完成相同的功能祷安。
解析邏輯流
1姥芥、駝峰式轉(zhuǎn)數(shù)組
首先將駝峰式轉(zhuǎn)為字串?dāng)?shù)組,是一道算法題汇鞭。標(biāo)準(zhǔn)庫提供了判斷字母大小寫的方法unicode.IsUpper(c)
算法:
- 遇到大寫凉唐,構(gòu)成一個單詞,存到res霍骄。
- 處理最后一組台囱。
end := len(s)
start := -1
for i, n := 0, end; i < n; i++ {
c := rune(s[i])
if unicode.IsUpper(c) {
// it doesn't count the last uppercase
if start != -1 {
end = i
words = append(words, s[start:end])
}
start = i
continue
}
end = i + 1
}
if end > 0 && len(s) >= end {
words = append(words, s[start:end])
}
2、校驗(yàn)HTTP請求是否合法
從app.router.AllMethod拿字段读整,列出了合法的HTTP請求簿训,
AllMethods = [...]string{
"GET",
"POST",
"PUT",
"DELETE",
"CONNECT",
"HEAD",
"PATCH",
"OPTIONS",
"TRACE",
}
并補(bǔ)入了框架自定義的"ALL", "ANY"也是合法的。
3米间、是否使用了By
功能相當(dāng)于spring中的RequestParam强品。即加入了By關(guān)鍵字,框架會從反射方法m *Method中拿輸入的形參作為RequestParam屈糊。解析http請求方法和路徑都不需要*Method的榛,而methodParser寫入了*Method字段就是為了解析輸入?yún)?shù)之用。調(diào)用如下方法解析參數(shù)逻锐。
(p *methodParser) parsePathParam(path string, w string, funcArgPos int) (string, int, error)
parse邏輯:
對于通配符Wildcard跳過處理夫晌。
從ParamType常中檢查請求參數(shù)名的類型是否合法雕薪。支持如下類型。
func LookupParamTypeFromStd(goType string) ParamType {
switch goType {
case "string":
return ParamTypeString
case "int":
return ParamTypeInt
case "int64":
return ParamTypeLong
case "bool":
return ParamTypeBoolean
default:
return ParamTypeUnExpected
}
把參數(shù)追加到路徑中晓淀,形式/{%s:%s}蹦哼。
4、解析路徑
strings.ToLower(w)轉(zhuǎn)為小寫要糊。
如此NamingGuide提供了RequestMapping RequestParam RequestMethod的功能。PathVariable功能妆丘,直接在path中用{}锄俄,需要自定義控制器方法。
最后仍和BeforeActivation中"手動"調(diào)用Handle()一樣勺拣,調(diào)用:
c.Handle(httpMethod, httpPath, m.Name)
路由注冊邏輯Handle
Handle的邏輯功能如下:
- 檢測上文提到的保留方法奶赠。
- 解析路徑中的參數(shù)名稱及類型,實(shí)現(xiàn)類似@PathVariable的功能药有。
- 參數(shù)構(gòu)建
- handler構(gòu)建
- 注冊路由到router.Router
先從c.routes拿已注冊路由的方法名毅戈,調(diào)用c.isReservedMethod(funcName)檢測已注冊路由。使用了保留字段的檢測方法愤惰,但功能上還是會檢測已注冊方法名的苇经。
方法調(diào)用用的參數(shù)由三部分組成:app.dependencies、controller字段和path路徑解析參數(shù)宦言。前兩個部分的整合已經(jīng)在構(gòu)建ControllerActivator時完成扇单,為深復(fù)制。這兩部分參數(shù)的特點(diǎn)是注冊時就能確定的參數(shù)奠旺,而后面的兩部分PathVaraible RequestParam是運(yùn)要運(yùn)行時才能確定的參數(shù)蜘澜。前文已經(jīng)提到RequestParam的參數(shù)名稱和類型(還未獲得值)已經(jīng)以/{%s:%s}的形式解析進(jìn)path。所以處理path中/{%s:%s}形式的字段响疚,將同時完成從@PathVariable @RequestParam獲得參數(shù)字段名鄙信,存入[]reflect.Value參數(shù)表。
由于@PathVariable @RequestParam的值需要在接到http請求之后才能獲得忿晕。并且未必提供完整的@RequestParam装诡,mvc框架中已添加是否獲得符合/{%s:%s}參數(shù)要求的校驗(yàn)。在server服務(wù)期間杏糙,接受到http請求慎王,請求path將通過已注冊路由,找到(w,*r)的handlers序列合集宏侍,具體邏輯流后面詳述赖淤。三類參數(shù)組合后,交由c.handlerOf()方法創(chuàng)建handler谅河。
上文提到自定義調(diào)用Handle方法可以注冊兩個handler咱旱,一個是從Handle()方法傳入确丢,一個就是路徑對應(yīng)的控制器方法。mvc規(guī)定傳入的handler優(yōu)先執(zhí)行吐限。
最后注冊路由鲜侥,同時注冊到ControllerActivator實(shí)例的c.routes用來檢測重復(fù)注冊。Handle邏輯的任務(wù)簡要:參數(shù)獲取诸典、handler構(gòu)建描函、route構(gòu)建。
handler構(gòu)建邏輯
調(diào)用handleOf()構(gòu)建邏輯狐粱。ControllerActivator實(shí)例會保存一個c.injector字段舀寓。每次調(diào)用handlerOf,就會將c.injector綁定為當(dāng)前值肌蜻。分別將ControllerActivator注冊為c.injector互墓,方法注冊為funcInjector。
需要說明的是蒋搜,以上所有的邏輯篡撵,對path的解析還只是一個模板,沒有填充實(shí)際的參數(shù)豆挽。這里用了閉包和反射的結(jié)合使用育谬,注冊將來才會調(diào)用的handler易于編程,結(jié)構(gòu)清晰祷杈。handler的注冊只是注冊了一個閉包斑司,而在接受請求、處理請求才調(diào)用handler時但汞,才執(zhí)行handler閉包宿刮,反射調(diào)用獲取運(yùn)行時字段信息。
給出兩個injector的數(shù)據(jù)結(jié)構(gòu):
StructInjector struct {
initRef reflect.Value
elemType reflect.Type
fields []*targetStructField
Has bool
Scope Scope
}
FuncInjector struct {
fn reflect.Value
typ reflect.Type
goodFunc TypeChecker
inputs []*targetFuncInput
Length int
Has bool
}
其中initRef私蕾、initRefAsSlice僵缺、elemType是同一個信息,區(qū)別僅是反射值踩叭、反射值切片磕潮、反射指針的指向元素。fields用于保存ControllerActivator中的字段容贝。field數(shù)據(jù)結(jié)構(gòu):
type field struct {
Type reflect.Type
Name string
Index []int
CanSet bool
AnyValue reflect.Value
}
這兩個struct的構(gòu)建過程自脯,完成了http請求參數(shù),向這兩個實(shí)體的注入斤富。完成第一步的注值之后膏潮,第二步進(jìn)行檢查,是否是BaseController满力,是否注入的控制器字段焕参,是否注入了方式的輸入?yún)?shù)轻纪。第三步根據(jù)不同的情況執(zhí)行反射調(diào)用call([]reflect.Value)。最后一步hero.DispatchFuncResult叠纷,將響應(yīng)相關(guān)的http信息寫入到ctx刻帚。
如果有ControllerActivator的綁定字段,則由ctx注值到c.injector.Acquire().Elem()涩嚣。情況1:對于BaseController的情況崇众,直接調(diào)用BeginRequest,EndRequest后返回航厚。情況2如果方法有形參校摩,則由funcInjector注值。
反射調(diào)用結(jié)果分發(fā)回context
反射調(diào)用將獲得reflect.Value類型的結(jié)果阶淘。調(diào)用hero.DispatchFuncResult分發(fā)結(jié)果。先用switch輪循[]reflect.Value處理statusCode互妓、content type溪窒、content、其它c(diǎn)untom類型冯勉。
分兩種情況澈蚌,如果反射調(diào)用的返回結(jié)果是字串,調(diào)用ctx.Write(content)灼狰。如果是Result類型宛瞄,調(diào)用Dispatch()寫入結(jié)果。其它情況:contentType為application/javascrip text/xml application/json的情況交胚。
type Result interface {
Dispatch(ctx context.Context)
}
以上所有無論何種情況最終調(diào)用net/http標(biāo)準(zhǔn)庫方法份汗,向ctx.Write([]byte)向ResponseWriter寫入結(jié)果。
ControllerActivator與app實(shí)例
最后總結(jié)一下路由注冊類ControllerActivator與主實(shí)例app的關(guān)聯(lián)蝴簇。在調(diào)用Handle()傳入了app.Router app.dependencies兩個字段杯活。app.dependencies字段指明了從http請求中需要拿參數(shù)的類型,在反射調(diào)用之前執(zhí)行熬词。
c.router字段的功能是:
- 提供c.router.Macros()提供http請求路徑解析的Macros旁钧。
- 調(diào)用c.router.Handle注冊已經(jīng)構(gòu)建的handler。這也是整個邏輯流的最終目的互拾,注冊一個傳入ctx的閉包到路由歪今。