分解uber依賴注入庫dig-原始碼分析

peng發表於2021-05-08

上一篇帖子 分解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資訊,再呼叫nodeCall方法進行遞規的呼叫。
形參有下面幾種型別

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可以包含 paramSingleparamGroupedSlice型別。

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可以包含resultSingleresultGrouped

容器

在呼叫container := dig.New()的時候就會建立一個容器,所有Provide進行註冊的函式都會組成容器的節點nodenode組成了`容器的核心

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)
}

相關文章