上一篇帖子 分解uber依賴注入庫dig-使用篇 把如何使用dig
進行程式碼示例說明,這篇帖子分析dig
的原始碼,看他是如何實現依賴注入的。
dig
實現的中心思想:所有傳入Provide
的函式必須要有除error
外的返回引數,返回引數供其他函式的形參使用。
比如上一篇的第一個例子裡,一個函式func() (*Config, error)
返回Config
另一個函式func(cfg *Config) *log.Logger
的形參使用了Config
整體呼叫流程
簡單說一下整體的呼叫流程,具體的細節再一點點展開說明。
傳入給Provide
裡的函式並不會直接被呼叫,dig
只會對這些函式進行分析,提取函式的形參和返回引數,根據返回引數來組織容器結構(這個後面會詳細說)。只有在呼叫Invoke
的時候才
會根據傳入的函式的形參進行查詢和呼叫返回這些形參的函式。還以上一篇的第一個例子進行說明
一共有兩個Provide
方法進行了函式註冊
c.Provide(func() (*Config, error))
c.Provide(func(cfg *Config) *log.Logger)
呼叫Invoke
方法c.Invoke(func(l *log.Logger))
,Invoke
方法,通過對傳入函式形參的分析,形參裡有*log.Logger
去容器裡找哪個函式的返回型別有*log.Logger
,找到方法func(cfg *Config) *log.Logger
,
發現這個函式有形參cfg *Config
再去找返回引數有*Config
的函式,找到了func() (*Config, error)
形參為空,停止查詢,進行函式的呼叫,把返回的*Config
傳遞給func(cfg *Config) *log.Logger
,進行
方法呼叫再把返回的*log.Logger
傳給c.Invoke(func(l *log.Logger))
進行函式的呼叫執行
所以在寫Prvoide
註冊函式的時候,順序隨便寫也不會問題,只要Invoke
時能查詢到相應的函式就可以。
上面簡單說了一下流程,提一個問題:如果是組引數,比如上一篇-組的例子只有多個函式返回了StudentList []*Student
group:"stu,flatten"``,在Invoke
時怎麼處理?
先留一個釦子,下面的內容會進行詳細說明。
分析傳入的函式
Provide
把函式新增到容器內,dig
會把傳入的函式進行分析,
利用go
的反射機制,提取函式的形參和返回引數組成一個node
,下圖是node所有欄位的詳細說明
主要看一下形參paramList
和返回引數resultList
兩個欄位
paramList
一個函式所有的形參資訊都會放入到paramList
裡
type param interface {
fmt.Stringer
// 構建所有依賴的函式,呼叫返回函式的值
Build(containerStore) (reflect.Value, error)
// 在生成dot檔案時使用
DotParam() []*dot.Param
}
Build
方法是很重要的一個方法,他會構建所有依賴的函式,呼叫返回函式的值,比如注入函式c.Provide(func(cfg *Config) *log.Logger)
的形參cfg *Config
會被解析為paramList
的一個元素,在呼叫Build
方法時,
會去容器裡查詢有返回*log.Logger
的注入函式的node
資訊,再呼叫node
的Call
方法進行遞規的呼叫。
形參有下面幾種型別
paramSingle
paramSingle
好理解,注入函式的一般形參比如int、string、struct、slice都屬於paramSingle
paramGroupedSlice
paramGroupedSlice
組型別,比如上一篇帖子中的例子
container.Provide(NewUser("tom", 3), dig.Group("stu"))
和
StudentList []*Student `group:"stu"`
都是組型別。
paramObject
paramObject
嵌入dig.In的結構體型別,比如上一篇帖子中的例子
type DBInfo struct {
dig.In
PrimaryDSN *DSN `name:"primary"`
SecondaryDSN *DSN `name:"secondary"`
}
paramObject
可以包含 paramSingle
和paramGroupedSlice
型別。
resultList
type result interface {
// Extracts the values for this result from the provided value and
// stores them into the provided containerWriter.
Extract(containerWriter, reflect.Value)
// 生成dot檔案時呼叫
DotResult() []*dot.Result
}
Extract(containerWriter, reflect.Value)
從容器裡提取到相應型別並給他賦值,比如注入函式c.Provide(func(cfg *Config) *log.Logger)
的*log.Logger
是一個resultSingle
,在呼叫Extract
時就是把reflect.Value
的值賦給他。
返回引數有下面幾種型別
resultList
node
的所有返回引數都儲存在resultList
裡
resultSingle
resultSingle
單獨的一個返回引數,注入函式的一般返回引數比如int、string、struct、slice都屬於他
resultGrouped
resultGrouped
組型別
比如上一篇帖子中的
container.Provide(NewUser("tom", 3), dig.Group("stu"))
和
StudentList []*Student `group:"stu"`
resultObject
resultObject
嵌入dig.Out的結構體型別,上一篇的例子中
type DSNRev struct {
dig.Out
PrimaryDSN *DSN `name:"primary"`
SecondaryDSN *DSN `name:"secondary"`
}
resultObject
可以包含resultSingle
和resultGrouped
容器
在呼叫container := dig.New()
的時候就會建立一個容器,所有Provide
進行註冊的函式都會組成容器的節點node
,node
組成了`容器的核心
type Container struct {
providers map[key][]*node
nodes []*node
values map[key]reflect.Value
groups map[key][]reflect.Value
rand *rand.Rand
isVerifiedAcyclic bool
deferAcyclicVerification bool
invokerFn invokerFn
}
providers map[key][]*node
這個key
是非常重要的一個引數,他是node
對應的函式的返回值
type key struct {
t reflect.Type
// Only one of name or group will be set.
name string
group string
}
name
命名引數和group
組不能同時存在,上一篇程式碼示例的時候就有說過。
看這一段程式碼
case resultSingle:
k := key{name: r.Name, t: r.Type}
cv.keyPaths[k] = path
// .......
case resultGrouped:
k := key{group: r.Group, t: r.Type}
cv.keyPaths[k] = path
}
其中的t: r.Type
就是返回值引數的型別,也就是說是providers map[key][]*node
這個字典,key
是返回值資訊[]*node
是提供這個返回值的函式,為什麼是個slice
,因為像組那樣的返回值是有多個函式提供的。
這裡要說一下組是如何做的,也回答上面留的問題,我們的示例程式碼
type Rep struct {
dig.Out
StudentList []*Student `group:"stu,flatten"`
}
if err := container.Provide(NewUser("tom", 3)); err != nil {
t.Fatal(err)
}
if err := container.Provide(NewUser("jerry", 1)); err != nil {
t.Fatal(err)
有多個函式返回了[]*Student
,dig會解析成
key{name: "stu", t: 型別的Type}
,做為字典的key
,有兩個Provide
裡的注入函式,
在呼叫Extract
方法時,給groups map[key][]reflect.Value
賦值
func (rt resultGrouped) Extract(cw containerWriter, v reflect.Value) {
if !rt.Flatten {
cw.submitGroupedValue(rt.Group, rt.Type, v)
return
}
for i := 0; i < v.Len(); i++ {
cw.submitGroupedValue(rt.Group, rt.Type, v.Index(i))
}
}
func (c *Container) submitGroupedValue(name string, t reflect.Type, v reflect.Value) {
k := key{group: name, t: t}
c.groups[k] = append(c.groups[k], v)
}