上一篇帖子 分解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)
}